home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
LSD Docs
/
LSD Docs.iso
/
FILEZ
/
lsd28.dms
/
lsd28.adf
/
AmigaCBeginners.2.doc.pp
/
AmigaCBeginners.2.doc
Wrap
Text File
|
1990-09-07
|
168KB
|
4,484 lines
CHAPTER 19 - COMPLEX DATA TYPES.
Now that we've listed all the important commands, we now come to the extra
capabilities of C. Among these are data types which can be configured
according to the demands and need of the user.
19.1 STRUCT.
struct {
char firstname[20];
char surname[30];
int age;
double income;
int sex;
} person;
The PERSON variable.
This function defines the variable person. The variable person consists of
several partial variables which are described in more details within the
braces. The first name has 20 characters, and the last name 30 characters.
There are also fields for age, income and sex. Similar to arrays, many
entries are collected under one name. The difference is that different
variable types appear within the structure. Accessing individual parts of
this variable requires more specification than arrays, which use an index.
The structure uses either the . (period) operator or -> operator. An
assignment of 30 to the element age appears as follows:
person.age = 30;
All other fields an be accessed in the same manner:
person.sex = 0;
person.income = 300000.0;
strcpy(person.firstname, "Rena");
strcpy(person.surname, "Bebewicz");
In order to use a pointer on such a construction, you must indicate the
data types. All youve done so far is to create a complete variable. You
need a name such as INT or FLOAT, through which other variables such as a
pointer can be defined. If several variables or pointers are used, it would
be better to create another data type which also has it's own name. This
can be done by indicating the type name after the STRUCT statement. If it
is called PERSON, the uppercase letter indicates that it isn't a variable.
Since variables, statements and functions must appear in lowercase letters,
and defines in uppercase and lowercase letters, structures can use a
combination of the two:
struct Person {
char firstname[20];
char surname[30];
int age;
double income;
int sex;
} person;
A pointer to this structure can now be initialised for access:
struct Person *pointer;
To acces one element of the structure the expression would be:
(*pointer).age = 30;
POINTER OPERATORS :
The parenthesis above control the higher precedence of the (.) operrator.
Usually a special operator is used as illustrated below:
pointer->age = 30;
This operator, made of a minus sign and a greater than character, is easier
to read. In addition, the arrow better illustrates it's purpose (it "points
to" something).
All operators which affect basic data types, such as defining arrays
(vectors), can be used on the newly created structure. The line below
provides 100 structures for storing partial variables:
struct Person occupant[100], *occ_upa;
Addressing individual entries can be done through an index
(oppupant[3].income = 25000.00), or after initialising the pointer with :
occ_upa = occupant;
COMMENT : The name represents the address of the first element. The
following could be written as an alternative :
occ_upa = &occupant[0];
The pointer can also be used to access the entries :
(pointer->income = 25000.00)
The pointer can be used to search the entire array. The following example
sets the pointer to the first free elements, provided that in an unused
entry the value zero was stored in AGE :
while(pointer->age)
pointer++;
Further applications of the struct directive will be discussed later.
PAGE 174
-----------------------------------------------------------------------------
19.2 BIT FIELDS.
The last remaining data type is the BIT FIELD. Bit fields are really a form
of structure definition. Unlike regular definitions, bit fields are usually
taken apart rather than created. A variable is defined which consists of a
certain number of bits. This variable always represents whole numbers. The
value range depends on the number of bits used. This number can be
calculated with the formula 2**number_of_bits. These fields are arranged in
INT objects so that the maximum field width is 16 bits. This also applies
to Lattice C which usually has a different concept of INT. If a field
doesn't fit into the partially occupied integer value, it goes into the
next one. A lot of memory can be saved by clever selection of the field
width.
struct {
unsigned sex : 1;
unsigned married : 1;
unsigned children : 4;
} data;
As in other structure definitions, the data types are placed inside braces.
to ensure that the bit field contains unsigned whole numbers, unsigned is
used (an abbreviation for unsigned int). A colon separates the field name
from the field width in bits. The definition above occupies two bytes (
size of a 16-bit integer), but this is not completely utilised. Since only
6 bits are used (1+1+4), an additional 10 bits can be assigned for other
applications without requiring additional memory:
struct {
unsigned sex : 1;
unsigned married : 1;
unsigned children : 4;
unsigned age : 7;
unsigned nr_cars : 3;
} data;
Additional data hass been stored which only occupies 2 bytes. Access to
each bit field occurs with the (.) operator.
data.children = 2;
The limited values must be respected, since in this definition no family
can have more than 15 children or operate more than 7 cars.
PAGE 175
----------------------------------------------------------------------------
19.3 UNIONS.
A special variable exists in C which accepts all conceivable data types.
This UNION is dimensioned by the compiler in a way that allows it to accept
all data types indicated in the definition :
Union universa {
int i;
double d;
struct Person;
char c[100];
} result;
All data types indicated can be stored in RESULT. It is useful to remember
what type is stored, for example :
result = 2.8;
or
strcpy(&result,c);
The memory requirements of such a variable depends of course on the length
of the largest entry. In the example above this would be 100 bytes used by
array C. Please note that only one type can be stored in this variable. The
author hasn't yet found a reason for using UNION structures instead of
solving the problem with other C data types.
PAGE 176
----------------------------------------------------------------------------
19.4 ENUM.
The C word ENUM defines a data type which assigns constant values to the
variables. Short for ENUMeration, ENUM lets you assign a consistent integer
number (constant) to a variable. This is useful for assigning numbers to
strings. The following example assigns numbers to the first three words in
COLOR and a specific value to the BLACK variable. Finally, the assignement
to the WHITE variable continues where the number assigned to BLACK left
off:
/* Definition of such a Data Type */
enum color(red,green,blue,black = 9,white)
/* Variable definition */
enum color var, *color_ptr = &var;
var = blue
if(*color_ptr == green)
*color_ptr == black;
The ENUM type assigns an integer value to each name starting at zero and
adding one for each element. With direct assignment values can be skipped.
The values defined in COLOR are :
red 0
green 1
blue 2
black 9
white 10
PAGE 177
----------------------------------------------------------------------------
19.5 TYPEDEF.
The TYPEDEF feature can be used to create new data names. A name assigned
with this command can be used as another data type during the definition.
The program below gives the word FLOAT the same meaning as the double data
type:
typedef double FLOAT;
The advantage of this directive is that TYPEDEF permits changes in the
entire program similar to define. Also large data types can be abbreviated
with this command. Look at the example below:
typedef char * STRING;
All pointer to char elements can be defined simply with the word STRING.
The typedef command has an advantage over the #define directive in that the
definition of the replacement occurs in a different way. One text can be
replaced with another text. A blank marks the spot in which the text
replacement appears. The redefinition of type STRING into another word
isnt possible since everything after the space behind CHAR already counts
as replacement text. With the TYPEDEF command this is just the reverse. The
last string STRING is the replacement type for the data type char*. It or
the blanks in the middle always belong to the definition of the data type.
This concludes the introduction to C keywords. These statements can now be
used in smaller programs. Practise make perfect. Experiment by changing one
or more parameters in the programs to determine the effect upon the
program. Write your own new programs once you've gotten use to entering the
ones already listed above.
PAGE 178
----------------------------------------------------------------------------
CHAPTER 20 - IMPORTANT CONCEPTS.
20.1 DECLARATIONS.
Declarations declare the data type of a variable or function in a program.
Varaious declarations have already been used. Directives can appear in which
you have to guess the data type that was declared, unless you examine the
expression for certain rules. First, some simple examples (the first 3 are
also definitions).
int i; Integer variable.
float array[20]; float variable.
double *ptr; Pointer to double element.
long func(); Function which returns a long value.
Every declaration basically contains an elementary data type (char,int,
float) which in certain case can be augmented by a special storage class
(auto, extern, register, static), or through attributes such as long,
short and unsigned.
extern doulbe sin(); Function from another file.
returns double value.
static short digit; Small integer variable.
register long i; Long-variable stored in a register.
Each name can also be equipped with combinations of *, [] or even (). The
asterisk to the left of the name represents the pointer character.
Parenthesis and brackets appear to the right of the name. They should not
contain values during declaration, because that turns the declaration into
a definition ([] for array and () for functions). Combining all parts of a
declaration can result in some complicated combinations :
long *field(); function which returns pointer to a
long value.
int *i_ptr[]; Field of integer pointers.
float(*berech)(); Pointer to function which returns float value
char *(*text)(); Pointer to function which returns pointer to
char.
int *(*text_arr[])(); Array of pointers to functions which return
pointers to int.
PAGE 181
----------------------------------------------------------------------------
Complicated expressions are formed with unified rules. Going through them
step by step makes the routine work later to decode such combiniations.
Youll need the table of operator precedence found in the Appendices. From
this can see that the parenthesis have higher priority than the pointer.
Look at the first example listed above :
long *field();
DECODING EXPRESSIONS.
First, the variable FIELD is a function. Look at the left side of the line
for the asterisk which defines the expression as a function. This function
returns a pointer. No additional information exists beyond the parenthesis.
The left side of the line identifies the data type LONG. Together this
information create a function which returns a pointer to the type LONG.
After processing one side of the line, the information to the right of it
must be processed (if priorities permit). The last and most complex example
look like this :
int *(*text_arr[])();
It looks complicated. It can be easily decoded by following the procedures
listed above, but the description will take a little longer.
Start with the name text_Arr. First test which operators are processed
first according to priority (to the right or left of the name). These are
the brackets which indicate an array. The operator has been processed on
the right, now go to the left. A pointer there indicates that this is a
pointer array. The right side of the line informs you that the pointers
should point to functions (the parenthesis are required because of the high
priority of parenthesis over the asterisk). Changing sides again, you note
that the function returns pointers. Since on the right side there is no
additional information, continue on the left with the data type. There it
shows that the pointers point to integers. This line declares an array of
pointers to functions which return pointers to INT. Complicated expression,
complicated sentence; but simple to analyse.
Only data types which cannot be used in definitions as values for passing are
prohibited. For example, not function can be declared which should pass
structures, arrays or functions. Pointers to such objects are permitted and
are the only way to access this information.
PAGE 182
---------------------------------------------------------------------------
COMMENT :
Some new compilers also permit stucture passing. This may differ from one
compiler to another. The expression &structure is always the address of the
structure, but STRUCTURE can represent different things. In the older
compilers this represents that starting address, like the expression with
the address operator. If structure is passed with a compiler that can already
pass the data structure,the entire data field is made available to the calling
function (not just the 4 bytes which represent the pointer to it).
PAGE 183
------------------------------------------------------------------------------
20.2 INITIALISATION.
This word should also look familiar from earlier chapters. Before you continue
here's a brief reminder of what initialisation does.
This expression designates the first value assignment of a variable. Before
using a variable it must contain a defined value. Otherwise the result of
calculations may be nonsense, or the system may crash. The initialisation can
be a direct assignment in the following form:
int i;
i = 0;
Or condensed into one line :
int i = 0;
The initialisation in the definition has the advantage of not requiring
additional assignments to set the variable. This saves time and memory.
Declarations, definitions and initialisations can be combined with one
data type :
double number, pi=3.1415926, sin();
C permits any constants and expressions during the initialisation. The
following assignments can be found in one line :
long number = x * pi - abs(y);
char *cp = string + strlen(string);
The variables used were already initialised or NUMBER would have contained
an undefined value.
Braces can improve visibility for arrays and structures, and separate
individual entries from each other. A pair of braces must be places before
and after the data which are to be transferred to the variables. A comma
follows the fields, even if the braces were used. After initialisation there
is a semicolon which is often omitted, causing compiler errors. Some examples
for correct structure definition:
struct CAR {
char make[16];
int hp;
int cylinder;
double price;
};
struct CAR will_have =
{
"bmw",
120,
4,
40000.00
};
PAGE 184
-----------------------------------------------------------------------------
Or collected together throught the structure definition:
struct CAR = {
char make[16];
int hp;
int cylinder;
double price;
} will_have =
{
"bmw",
120,
4,
40000.00
};
The examples of multi-dimensional array initialisation were already discussed
in the chapter about arrays and pointers. Limitiations because of memory
classed were also mentioned. Initialisation is only permitted for Global,
external or static variables. If an auto variable appears within a function
which corresponds to this initialisation e.g.:
char message[] = "Remember the initialisation!";
This can be fixed with a pointer definition:
char *message[] = "Remember the definition!";
There are no limitations if the lower definition is used as a pointer
variable. Another possibility is the use of STATIC variables. It doesn't
matter whether the string is stored in a STATIC or AUTO variable. One small
word but a big difference.
static char message[] = "Remember the Initialisation!";
PAGE 185
----------------------------------------------------------------------------
CHAPTER 21 - POINTER ARRAYS.
You worked with pointers and arrays in earlier chapters. As the title
indicates they can be combined to construct an array of pointers. You may be
wondering what you can do with a pointer array. If you marketed an existing
program in a foreign country, youd have to find every piece of text in the
source code and translate the text into that foreign language. It would be
simpler and safer to store all the text in one area of the program or even
in a separate file, and let the translator change it from there. A pointer
array would point toward that area or file.
Lets start with a list of error message that the user might see after entering
incorrect input. The use of error numbers makes sense, since some errors occur
at several different locations. In the current program portion passign the
error number to the error routine is sufficient because the function should
do the rest.
One solution to this problem would be a function that tests the occurence of
this error or another error, and displays a message if necessary.
error(e_number)
int e_number;
{
switch(e_number)
{
case 0:
puts("Everything OK, no error!");
break;
case 1:
puts("wrong key activated!");
break;
case 2:
puts("Please insert Diskette!");
break;
default:
puts("Unknown error occurred!");
}
}
PUTS:
This is a faitly complex implementation of a function which requires another
PUTS call for each additional error message. The PUTS function displays a
string on the monitor, without the options available in PRINTF. It is a
litte faster than the general output function, but various CASE statements
must be added, which slow down the program. Since every number can be
assigned a certain error message (string), it should be possible to use the
error number as an index to a field of strings. Since a string is usually
stored in char error[81], the memory for the text is defined as a 2
dimensional array :
char error[32][81];
PAGE 189
-----------------------------------------------------------------------------
Now there is space for 32 strings with a maximum length of 81 characters each.
This formulation permits the following routine for error message output:
error(e_number)
int e_number;
{
puts(error[e_number]);
}
But the work which was saved here must be completed elsewhere. Each string
must be initialised with the STRCPY function. The following command sequence
shows how:
strcpy(error[0],"Everything Ok, no error!");
strcpy(error[1],"Wrong key Pressed!");
strcpy(error[2],"Please Insert Diskette");
It doesnt matter whether text is called with STRCPY or PUTS. A disadvantage
of this method is that memory gets allocated elsewhere. The definition
allocates 81 characters, including a null byte for every error message
even if a message only requires 20 bytes. This doesnt matter too much in
the Amiga but the user shouldn't develop bad programming habits. If you do
the same thing for a text in which each word is assigned an entry, memory
rapidly fills with garbage.
As a last resort, the string arrays can solve almost all the problems
mentioned above. The definition of a string array is as follows:
char *error[32];
The pointer can be set to the beginning of an error message and can make the
message length dependant on a fixed array length. The next string starts
immediately after the last character of the previous string. This avoids
initialisation.
You may remember that a program in chapter 15 pointed to a string within
program text. This can occur during pointer definition; the entire pointer
array is initialised with the starting addresses of the strings. The error
messages must be defined a GLOBAL if this turns out to be the case:
/* error_msg.c 21 */
char *error[] =
{
"Everything Ok, no error!",
"Wrong key activated!",
"Please Insert Diskette"
};
PAGE 190
-----------------------------------------------------------------------------
main()
{
/* DISPLAY ALL ERROR MESSAGES */
int i, error_msg = sizeof(error) / sizeof(char*);
for(i=0; i<error_msg; i++)
printf("Error Number %d: \"%s\"\n",i, error[i]);
}
Since the number of error messages are not counted, they aren't indicated
during the definition. Because of this, additional text can be entered
between the braces without making changes. In the actual program, however,
the number must be calculated. The memory requirement of ERROR can be
obtained from SIZEOF. The ERROR function is now an array of pointers. Now
SIZEOF reports that the variable consumes 12 bytes. That is the memory
requirement for any pointer, and has no bearing on the memory needed for
the text. The 12 bytes are divided by the space requirement of a char
pointer (which is 4 bytes). The result is the number of pointers, the
maximum index minus one (the indexes start with 0).
PAGE 191
----------------------------------------------------------------------------
CHAPTER 22 - USEFUL MACROS.
Much work has already been done with #define. The substituted text called a
macro, was kept simple; one word exchanged for another. But that's only half
the job, macros also allow you to pass arguments. Some useful macros have
been developed to make parameter passing easier for the user.
CONSTITUTION OF A MACRO :
Functions that do little and have concise coding can be written as macros.
Since macros replace the original text, the compiler translates the C code
directly to machine language at that location. Function calls or parameter
passing is not required. The required directives are located at the exact
location in the program. This makes the macros faster and more efficient than
the function calls. If used frequently, however, macros make the program code
much larger. The same operations are repeatedly stored in identical form at
the exact place where they are needed in the program code. An advantage of
macros is that they are usually independant of data types. This condition
can be seen in the example of the MAX macro created earlier in the
book :
#define MAX(a,b) ((a>b)? a:b)
If variables i1 and i2 have been defined as integers, the following macro
results :
result = MAX(i1,i2);
From the preprocessor :
result = ((i1>i2)? i1 : i2);
The variable result is also an integer value. If i1 and i2 were defined as
FLOAT values, the same expression occurs, but a float number is returned
for result. In text replacement it doesn't matter which data type was used.
This is impossible with functions because the data types are specified for
parameters that are passed. This is extremely easy to use.
PAGE 195
----------------------------------------------------------------------------
2.2.1 MACRO ERROR SOURCES.
Improperly implemented macros harbour some dangers which can lead to errors.
These errors can be extremely difficult to detect, but most can be prevented
with little effort.
If the macro call just defined included some parameters which contained
operators, errors could appear. For example :
result = MAX(i1 / 2, i2);
is converted as usual into :
result = ((i1 / 2 > i2)? i1 : i2);
The > comparison operator has a higher precedence than the | character. This
means that first a test is made to see whether i2 is less than 2. The result
of this logical comparison (1=TRUE , 0=FALSE) is then combined with i1 using
OR bit by bit. This could not occur in the basic calculations since they have
a higher precedence than the comparison operators (see Appendices for a table
of all precedences). A simple remedy is to place all parameters found in the
macro inside parenthesis. Use this definition :
#define MAX (a,b) (((a)>(b))? (a) : (b))
Side-effects caused by calculations and value changes can also cause problems.
A simple example is the following short program which should calculate the
squares of numbers between 0 to 10.
/* bad_macro.c 22.1 */
#define QUADRAT(x) ((x)*(x))
main()
{
/* Wrong use of a macro */
int i = 0;
while(i<=10)
{
printf("The square of %d ",i);
printf("is %d\n",QUADRAT(i++));
}
}
The output of the program is :
The square of 0 is 0
The square of 2 is 6
The square of 4 is 20
The square of 6 is 42
The sqaure of 8 is 72
The square of 10 is 110
PAGE 196
----------------------------------------------------------------------------
Wheres the mistake?. Examine the material left by the preprocessor for the
compiler. The line with the macro is the important line:
printf("is %d\n",QUADRAT(i++));
It becomes :
printf("is %d\n",((i++)*(i++)));
If the square of 2 (i=2) is computed, the following expression is what was
calculated and passed to the PRINTF function:
2 * 3
The variable is incremented BEFORE the multiplication, and the second
multiplier is wrong.
You may have wondered why the program uses two printf calls instead of making
do with one. There is another error source here which must be considered as
a separate entity, otherwise this overview would not cover all possible side
effects. To demonstrate the errors which occur in printf functions, lets try
this with same function, but without a macro:
main()
{
/* Wrong use of a macro */
int i = 0;
while (i<=10)
{
printf("The square of %d is %d\n",i,i * i++);
}
As a result you get two squared numbers which dont match the desired numbers.
4 is offered as 3's square (the square is only calculated for the preceeding
number). This is caused by the parameters being placed on the function's
stack (temporary storage), where the function expects to find them.
Unfortunately the storage of these values occurs in reverse order, i.e. first
i*i++ is stored and i is incremented. Then the first parameter of i which
already has the wrong value appears.
C language offers many routes to writing short and efficient programs. However
there is the danger of the new programmer trying too much, too soon. The side
effects shouldn't occur in functions or macros unless you know their effects
on all variables and parameters. Again, make use of the precedence table as
needed (see appendices for this table.)
PAGE 197
-----------------------------------------------------------------------------
22.2 LIBRARY MACROS.
Frequently used macros are best stored in a library, from which they can
easily be inserted in source code with #include. This group includes various
conversion functions for letters (e.g. testing is a letter is upper or lower
case). The following defines have the following tasks:
Convert uppercase letters to lowercase letters :
#define to_lower(c) ((c)+32)
Convert lowercase to uppercase letters :
#define to_upper(c) ((c)-32)
Test for letters (1=yes,0=no):
#define isalpha(c) ((c)>='A' && (c)<='Z' || (c)>='a' && (c)<='z')
Test for uppercase letters (1=yes / 0 = no)
#define isupper(c) ((c)>='A' && (c)<='Z')
Test for lowercase letters
#define islower(c) ((c)>='a' && (c)<='z')
Test for number (yes = 1, no = 0)
#define isdigit(c) ((c)>='0' && (c)<='9')
Test for alphanumberic characters.
#define isalnum(c) (isalpha(c) || isdigit(c))
Test for blank,tab,linefeed,carriage return or formfeed.
#define isspace(c) ((c)=='' || (c)=='\t' || (c)=='\r' ||
(c)=='\n' || (c)=='\f')
Test for special characters.
#define ispunct(c) ((c)>=' ' &&!isalnum(c))
Test for printable characters.
#define isprint(c) ((c)>=040 && (c)<=0176)
PAGE 198
---------------------------------------------------------------------------
Test for control characters.
#define iscntrl(c) ((c)>= 0 && ((c)==0177 || (c)<' '))
Test for ASCII characters.
#definfe isascii(c) ((c)>=0 && (c)<=200)
These defines should be easily understood once they are examined. They should
be written into a file name CTYPE.H, unless this type of file is already
available in a subdirectory. If the following line occurs in a program you
know what should be found there:
#include<ctype.h>
Remember that for the test for letters, only the 26 letters of the alphabet
are considered. International special characters are not viewed as letters.
Maybe, they can be implemented in a suitable manner. Perhaps the STRCMP
can be converted with the new defines.
char *s, *t;
int n, compare;
compare = strcmp(s,t);
compare = strncmp(s, t, n);
compare = stricmp(s,t);
compare = strnicmp(s, t, n);
The first function is identical to the routine you programmed. If compares
two strings and returns the result of the comparisons. In the second function
STRNCMP, the third value indicates up to what point the comparison should be
made. The comparison can be limited this way up to n characters. For example
only the first four elements. The functions which have an i in the name
dont differentiate between upper and lower case letters. Comparing the two
strings "aBcDeF" and "ABcdeF" with the function returns zero because both
strings are equal.
PAGE 199
----------------------------------------------------------------------------
CHAPTER 23 - COMMUNICATION.
The programs written so far have only displayed data on the screen or
requested keyboard input. It's time to communicate with other devices. The
CLI is the easiest way to transfer data. In this chapter we'll communicate
with the CLI as well as other devices.
23.1 PASSING DATA WITH THE CLI.
All programs are called from the CLI by entering the filename and arguments
(if needed). Here is one type of call to invoke ED:
ED file.c SIZE 50000
This complete line can be made available to the called program, though not in
this form. The operating system modifies this line slightly.
Now comes the question of data transfer. The main function containing two
arguments controls this. Until now every call appeared as follows :
main()
{
. . .
. . .
}
THe next code passed two values from the calling program from either the CLI
or a MAKE file. The first value represents the amount of information amd a
pointer to a CHAR pointer. This sounds somewhat complicated, but looking at
the input line of the program should make it clear. First the new version of
MAIN two arguments:
main(argc,argv)
int argc;
char *argv[];
{
. . .
. . .
}
The name of our fictional program is PRG. Look at this sample entry:
prt Text1 parameter 3 -pi
PAGE 203
-----------------------------------------------------------------------------
After the program call, the variable ARGC contains the number of arguments
(5). Why is 5 passed when only 4 arguments are available? The fifth argument
comes from adding the program name used during the call. The name ARCG
(ARGument Count) is a random choice since it is an auto variable of the
MAIN function. The name ARGV (ARGument Vector) handles vectors.
Spaces or tabs separate every argument of the input line. After the call the
pointers of *argv[] point to :
argv[0] "prg"
argv[1] "Text1"
argv[2] "Parameter"
argv[3] "3"
argv[4] "-pi"
Let's examine the data. Use the following program to print the data:
/* arg_test.c 23.1 */
main(argc,argv)
int argc;
char *argv[];
{
while(--argc >= 0)
puts(*argv++);
What can be done with this?. You can access a small math program by entering
the following in CLI:
compute 123.5 * 4711
The following program is written so that it will only perform simple
calculations consisting of two numbers and an operator. Feel free to improve
on the program as needed:
/* arg_math.c 23.1 */
extern double atof(); /* Declaration */
int error = 0;
main(argc,argv)
int argc;
char *argv[];
{
double result, value();
if(argc !=4)
printf("\nWrong Entry\nCall: Number1 # Number2\n");
else
{
result = value(argv[1], argv[2], argv[3]);
if(!error)
printf("\n%s %s %s = %.91f\n", argv[1], argv[2],
argv[3], result);
}
}
PAGE 204
---------------------------------------------------------------------------
double value(number1,op,number2)
char *number1, *op, *number2;
{
double z1 = atof(number1);
double z2 = atof(number2);
switch(*op) /* Only the first character */
{
case '/':
return(z1 / z2);
case '*':
return(z1 * z2);
case '+':
return(z1 + z2);
case '-':
return(z1 - z2);
default:
printf("\nUnknown Operator >%s<\n",7,op);
error = 1;
return(0.00)
}
}
LATTICE :
The mathematical function/floating point library must be linked to the
standard library because it contains the atof function. Example:
lc -Lm math2
AZTEC :
If you work with Aztec C, the mathematical function/floating point library
must be linked to the standard library C.LIB since it contains the ATOF
function. Example:
cc +L math2.c
ln math2.o -lm -lc
PAGE 205
-----------------------------------------------------------------------------
23.2 BUFFERED INPUT / OUTPUT.
Many programs require permanent data storage for files, whether it is a
database or a word processor. These require routines that control input /
output with external devices such as printers, disk drives, RS-232 interfaces
or a hard disk drive. The operating system provides various functions for this
purpose. These routines can be divided into 2 groups; buffered input/output
and unbuffered input/output.
This type of data transfer does a lot of work, even though it may not seem
evident at first. For example, all data selected for transfer to disk goes to
a buffer first. When this buffer completely fills, the data goes to the disk.
The question is, why do it this way?
A disk drives reads and writes information much slower than the computer can
send or receive it. This is caused by the mechanics of the disk drive. Before
writing any data, the read/write head must move to the track where the data is
stored. Then it must wait until the disk rotates to the right location. Only
then can the data be written. Although this timing is brief in human terms,
the computer (actually its CPU) is kept waiting a very long time.
DISK BUFFERS:
If you transmitted every character with this method, the computer would spend
more time waiting to place a single character on the disk drive than
performing any other task. For this reason, smaller amounts of data move to an
area of memory in the computer before transmission. Once this buffer fills,
the data moves to the disk drive. This reduces computer waiting time. As soon
as the drive writes the first character to the proper place, it can place the
other data right behind it and place the complete buffer in one pass. This
reduces the number of disk accesses, which accelerates program execution.
The same principle is also used for reading data.
The functions which perform this job are GETC and PUTC, which like their
relatives GETCHAR and PUTCHAR, input or output a character.
These functions are not part of the C language. For this reason they can be
found in a library, or in this case as a #define in the Header file (.h).
The definition of GETC and PUTC can also be found in the STDIO.H file with the
familiar PUTCHAR and GETCHAR.
#define getc(p) (--(p)->_rcnt>=0? *(p)->ptr++:_filbp(p))
#define getchar() getc(stdin)
#define putc(p) (--(p)->_wcnt>=0? *(p)->_prt++:_flsbf((c),p))
#define putchar() putc(c,stdout)
PAGE 206
----------------------------------------------------------------------------
Lets clear some of this up. The definitions of PUTCHAR and GETCHAR are all
you need to know for now. Both can be traced back to GETC and PUTC and
represent special versions of the two functions.
Before the first character can be moved with these functions, a channel must
be opened. A channel is just a data line to a certain device. No cables
actually open up a channel, but the system knows where to send the
information. A special code obtained from the operating system during the
opening of the channel allows addressing the device at any time. Devices and
individual disk files can be addressed.
Several files can be accessed on the same drive without having data conflict.
The file pointer indicates the channel. Since a buffer is used for input /
output, the computer must be informed of where the data should be stored
intermittently and how large a space must be reserved. A structure named
FILE (notice the uppercase letters) defined in STDIO.H contains all necessary
data for buffered input / output. The following listing writes a file and
then reads it again:
/* fprint-fscan.c 23.2.1 */
#include <stdio.h>
main()
{
FILE *input, *output, *fopen();
char filename[81], text[200];
printf("Please input file name!\n");
scanf("%80s",filename);
printf("Input a (long) word!\n");
scanf("%200s",text);
output = fopen(filename,"w");
printf("Filehandle %d\n", output);
fprintf(output,"%s",text);
fclose(output);
input = fopen(filename,"r");
printf("Filehandle %d\n", input);
fscanf(input,"%200s",text);
fclose(input);
printf("The Text: >%s<\n",text);
}
THE FOPEN FUNCTION :
The FOPEN function opens the channel and returns the file pointer, used for
all future access to this file. Since FOPEN returns something other than an
integer, it must be declared as a function which returns a file pointer.
This routine requires two arguments; the name of the file and the access mode.
The user can enter the name. The access mode tells the computer what should
be done with the file. The mode can be one of three letters :
PAGE 207
----------------------------------------------------------------------------
r - opens a file for reading.
w - opens a file for writing.
a - opens a file for adding additional data.
A file opened for reading (r) can only read data not write data. A file
opened for writing lets you write data. The append mode writes data to the end
of an existing file. In normal write (w) mode, writing starts at the beginning
of the file and overwrites existing data. This can easily lead to loss of
data. The example above overwrites and existing file and destroys some
previouslt stored data.
After the opening the file is ready for writing. The file handle appears on
the screen. The SCANF function assigns the characters entered throught the
keyboard to the string TEXT. The FPRINTF function can write data to a file. It
is almost identical to the printf routine, but differs in the first argument.
Before the command string, a file handle must be passed to assign the
information to the correct file. After writing, the file closes. This step is
very important because of the buffer. All input/output goes there for
intermediate storage until the buffer is filled. Some of the data input can
still be stored in that buffer. If the user assumes that everything was stored
on the disk and switches off the computer, the data still in the buffer would
be lost. For this reason the FCLOSE call closes the channel after writing the
remaining buffer contents to the open file.
Now the file re-opens again, but this time for reading which is signated with
the mode 'r'. Since keyboard input always uses SCANF, the FSCANF is used here.
First the file pointer and the arguments of the scanf routine must be passed
to the read function.
The correct closing of the file follows. If you ommitted this instruction data
loss cannot result. However, it's good practise to close and opened file
immediately after use, not only because it's good housekeeping, but also
because a computer can maintain only a certain number of open files. If more
files are opened, a channel cannot remain open at a certain time. Should an
error occur, because the operating system cannot make a channel available,
the file pointer is returned as zero. This happens when no channels are
available or the file which should be read does not exist.
The next example program is a small copy program. It is called with arguments
and is therefore able to accept arguments from main.
One difficulty must be avoided. Nobody knows in advance what type of data will
be transmitted. The fscanf cannot be used since it has to indicate if strings
or numbers are used. The program can only read one character at a time. The
scanf function with the format instruction %c can be used, but the FGETC
function works much better. It reads a character from an input file and is
much faster than the FSCANF function. The FPUTC statements performs the
output.
PAGE 208
------------------------------------------------------------------------------
After opening the two files, a character is read and displayed immediately ,
until ...... You dont know when all the data has been copied How can you
detect when the last character has been read?. The problem has already been
solved. FGETC returns a special character if no additional information is
available - end of file (EOF). The #include file STDIO.H contains this text
as a #define so that the incoming characters only have to be compared with
EOF.
EOF is stored there as -1. This has consequences which at first are not
evident. Valid data have codes which in FGETC are between 0 and 255. Wnen -1
appears, no CHAR variable can be selected to accept the character. Either
negative numbers are ignored or data is lost. For this reason, int variables
are used even if only a CHAR element is stored in them.
/* Copier.c 23.2.1. */
#include <stdio.h>
main(argc,argv)
int argc;
char *argv[];
{
long copy(); /* If it is interesting. */
if( argc != 3)
{
printf("Bad Arguments!\n");
printf("From_file to_file\n");
}
else
copy(argv[1],argv[2]);
}
copy(fromfile,tofile)
char *fromfile, *tofile;
{
FILE *input, *output, *fopen();
register long counter = 0;
register int c;
if(!(input = fopen(fromfile,"rb")))
/* Open a binary file */
{
printf("%s cannot be opened!\n",fromfile);
}
if(!(output = fopen(tofile,"wb")))
{
printf("%s cannot be opened!\n",tofile);
fclose(input); /* Was OK */
return 0 ; /* zero */
}
while((c=fgetc(input))!=EOF)
{
fputc(c,output);
counter ++;
}
fclose(input);
fclose(output);
printf("\n%ldBytes copied!\n",counter);
return(counter);
}
PAGE 209
-----------------------------------------------------------------------------
The copy program is called as follows:
copier [d:] [\path]name1 [.ext] [d:][\path] name2 [.ext]
Everything written into the brackets is optional and can be omitted. Only two
filenames must be provided. If an error should occur during the opening of
the two files, an error message appears. An error message is also displayed if
too many or too few arguments are passed.
Since it is very slow, the program is unsuitable for everyday use. However,
it's a suitable demonstration program. Let's limit the discussion to the
essentials. The files are opened as binary files with WB and RB (Aztec
compiler users should omit the B; the file always opens as a binary file).
For example, this prevents any conversions being attempted which could occur
in text files. During reading all \r characters (carriage return) are erased
automatically and characters with code 26 (<Ctrl><z>) are converted to EOF.
During write the linefeed (\n) is converted to a character combination
(\r\n). Opening the file with the appendix b (binary) returns all characters
as they are stored in the file and not converted.
PAGE 210
-----------------------------------------------------------------------------
23.3 MORE BUFFERED INPUT / OUTPUT.
Besides FGETC,FPUTC,FSCANF and FPRINTF there are some other important
functions that use the internal buffer. Among them are FREAD and FWRITE.
These routines transport any number of bytes. For this reason two additional
arguments are required for FREAD and FWRITE. One argument is the area which
serves as the bufferand the other the size of the units to be transmitted.
This needs some explanation. The buffer in previously used functions was
always located in the FILE structure. Since only small amounts of data were
transported, the buffer did not have to be too large (512 bytes). Since the
user can now determine how much data is transmitted, the buffer may be too
small. For this reason the use must define the memory area, thereby setting
the maximum size of the data transfer. The data size must also be indicated.
In GETC and PUTC only 2 character can be transmitted (1 byte) and the size of
the CHAR object doesnt have to be indicated. If a long value, instead of a
character, is stored, 40 bytes must be transmitted for 10 of these values.
The object size (in this case 4 bytes each) is the second argument that
must be passed. If the data type is unknown, the SIZEOF operator should be
used because it returns the correct value.
Besides these two arguments the number of units to be transmitted (char,int
structure ...) and the file pointer must be transmitted. The buffer size
determined at the time of definition should not be too small. A call of this
function appears as follows :
Datatype buffer[element]; /* Definition of the buffer */
fread(buffer,sizeof(Datatype), Element, file_ptr);
The first argument is the buffer from which the data is read. The buffer
should be the same type as the units to be transmitted. The second value is
the unit size. This unit is a data package which can be transmitted as one
item. If long variables are transmitted it makes sense to indicate 4 bytes
as the data block length, since 4 byte units are the normal requirement.
If 100 double variables are stored, 100 is placed at the element. Finally,
a file pointer is added, which was received from FOPEN.
If data is read or written, the argument sequence and type remains the same.
The FWRITE function stores the data using this syntax :
int table[876], size = 876;
FILE *output_ptr;
fwrite(table,sizeof(double),size,output_ptr);
PAGE 211
-----------------------------------------------------------------------------
If the value of size is not specified, either through a variable or with a
#define, the SIZEOF operator can be read:
sizeof(table)/sizeof(int)
The function returns the numbers of complete transmitted data packages. This
ensures storage of all data. During copying, data can be read until the
number of requested data differs from the data delivered. If FREAD returns
a zero, the last data was read. A sample program:
/* fread.c 23.3.3. */
#include <stiod.h>
#define NUM_DATA (sizeof(data)/sizeof(long))
Long data[] =
{
4711, 815, 1024, 1, 31415926, 0, -13 ,10,
0xFFFF, 065432
};
main()
{
FILE *input, *output, *fopen();
int i;
long test[NUM_DATA];
char filename[81];
printf("Please input filename ! \n");
scanf("%80s", filename);
printf("Data size %d, Elements %d\n",sizeof(data), NUM_DATA);
output = fopen(filename,"wb"); /* Binary ! */
fwrite(data,sizeof(long),NUM_DATA,output);
fclose(output);
printf("Read data!\n");
input = fopen(filename,"rb");
printf("%d Elements Read\n",fread(test,sizeof(long),
NUM_DATA,input));
fclose(input);
for(i=0; i < NUM_DATA,i++)
printf("%ld\t",text[i]);
printf("Done!\n");
}
This program writes a long array to the file after entering the filename. The
array size and the number of elements appear on the screen. The define
NUM_DATA stores all data on the disk. After closing the file, another array
accepts the data read. All data appears on the monitor.
Lattice C users must open the file as binary, or internal conversion produces
false values in the variables. If FWRITE and FREAD are used, the file must
also be opened as a binary file.
PAGE 212
-----------------------------------------------------------------------------
23.4 UNBUFFERED INPUT / OUTPUT.
Besides the buffered functions just demonstrated, there are other routines
which do not require a buffer. The data to be transmitted don't have to be
stored in a buffer, but can be stored immediately. This cancels all the
effort required for the buffer and internal pointers. This also cancels the
need for a file pointer through which the operating system can access the
buffer. A channel number assigned during the opening must be used as
identification. This channel number is stored in an integer variale and
replaces the file pointer in all calls. The OPEN function opens a file.
Filename and filemode (as an integer) arguments pass to the function. In
FOPEN the mode must be a string, while in OPEN the mode is one of three
values: 0,1,2 or 8. These numbers correspond to the strings r,w and a.
0 - Opens a file for reading.
1 - Opens a file for writing.
2 - Opens a file for reading and writing.
8 - Opens a file for appending.
Instead of these numbers DEFINES can be used to make the program more
readable. They are stored in a header file name FCNTL.H and are defined as
follows :
#DEFINE O_RDONLY 0
#DEFINE 0_WRONLY 1
#DEFINE 0_RDWR 2
#DEFINE 0_APPEND 8
To use these defines, the file must be included in the source with the
sequence:
#include <fcntl.h>
Another difference from the FOPEN routine is that OPEN always assumes the
existence of a file. The following call creates a file even if the file
already exists :
fopen("filename","w");
The OPEN function always prompts for the name of an existing file. The CREATE
function must be used to create a new file.
The CREATE function returns an integer (the file handle). You dont need to
call OPEN. If an error occurs and the file cannot be opened, both CREATE and
OPEN return the value -1. Before using the returned value as a file handle,
check the file handle for a value of -1, or the system will crash.
PAGE 213
-----------------------------------------------------------------------------
The key combination of <Ctrl><Commodore><Amiga> resets the computer but
deletes any data in the RAM disk.
A file opened in this manner allows writing using the WRITE function instead
of the FWRITE function. Since this method uses no buffer, only certain
input/output functions can be accessed. Also, the READ function replaces the
FREAD function. Buffered functions have an F in front of their name (e.g.
fopen, fread, fwrite etc..). Unbuffered functions omit the F. The CLOSE
function closes an unbuffered file.
The following function stores a list of DOUBLE numbers :
/* write-read.c 23.4 */
#define NUMBER (sizeof(data)/sizeof(double))
double data[] =
{
1.5, 2.0, 3,14159265, 2.718281828,
};
main()
{
int handle, dummy = 0, i, actual;
double data2[NUMBER];
char filename[81];
printf("Please input filename :\n");
scanf("%80s",filename);
handle = create(filename,dummy); /* Create new *./
if(handle != -1) /* mEverything OK */
{
actual = write(handle,data,sizeof(data));
printf("Desired %d Bytes, Actual %D bytes\n",
sizeof(data),actual);
close(handle);
}
else
printf("Error during Creation of %s\n",filename);
handle = open(filename,0,dummy); /* Read */
if(handle != -1) /* Everything ok */
{
actual = read(handle, data2, sizeof(data));
printf("Desired %d Bytes, Actual %d Bytes\n",
sizeof(data), actual);
close(handle);
}
else
printf("Error during opening of %s\n",filename);
for(i=0,i < NUMBER,i++)
printf("%.81f ",data2[i]);
printf("Thats all ! \n");
}
PAGE 214
-----------------------------------------------------------------------------
The OPEN and CREATE functions, which return a file handle, have a peculiar
variable named DUMMY. This variable respresents a value which may be
unneccessary, but the compiler checks for the variable if needed. The value
stored in dummy, as the name suggests, has no significance. In OPEN the
second argument represents the DUMMY value for reading or writing.
The WRITE and READ functions have one less argument than FWRITE and FREAD.
Furthermore (and this is important), the file handle is placed at the
beginning, not at the end as in buffered functions. The unbuffered functions
transfer data one byte at a time only. This means that the size indication
is unnecessary. The return value is the number of bytes transmitted so far.
PAGE 215
----------------------------------------------------------------------------
23.5 DIRECT ACCESS.
The following functions provided the user with the ability to directly access
certain characters in a file. The difference from the usual read lies in the
fact that not every character must be read starting at the beginning of the
file until the program finds the particular characters. To access the last
10 characters in a file of 1000 characters, 990 characters would have to
be read first. With direct access, the command starts the reading after the
990th character. A file pointer always points to the last accessed data.
During a sequential read or write, when one character after another is
processed, this pointer always increments by one. The functions LSEEK and
FSEEK let the user set this pointer to any desired position. Both routines
require 3 arguments for this, where LSEEK is the unbuffered version and FSEEK
is the buffered version. For this reason LSEEK requires the file handle as
the first arguments, while FSEEK requires a file pointer. A second value
follows, the number of bytes by which the pointer must be moved. Positive
values move the pointer toward then end of the file, negative values toward
the beginning of the file. This value must be passed as a LONG value.
The third argument, an integer, indicates from which position the movement
should start. A 0 sets the data pointer to the beginning of the file, 2 to
the end of the file and 1 to the current position. Some examples :
lseek(f_handle,100L,0);
The data pointer moves to position 100 (i.e. 100 characters from the start
of the file). If now points to the 101st byte of the file. The following
function places the pointer at position 70, since the call passed the value
1:
lseek(f_handle, 70L,1);
The 1 indicates the calculation of the new pointer position from the current
location. To move the pointer 20 characters toward the beginning of the file,
the following function is required:
lseek(f_handle, -20L,1);
If the processing of a file should start from the end of the file, the pointer
can be set to the last position of the file with :
lseek(f_handle, 0L,2);
This shows that in mode 2 (end of the file), only negative numbers or zero
are permitted since the pointer is already at the end of the file. In mode
0 only positive numbers or zero can be used. These functions only move the
file pointer.
PAGE 216
-----------------------------------------------------------------------------
The data must be read or written with the various functions such as READ or
WRITE.
The LSEEK function returns the value of the data pointer after the move. FSEEK
returns either 0 or -1. With a -1 an error occured, otherwise everything
proceeded without a problem.
Two additional routines, a buffered and an unbuffered version can sense the
current value of the data pointer. Since it is a long value the FTELL and
TELL functions must be declared first. The only required argument of both
functions is the proper file handle.
The function call could be replaced with the call :
lseek(f_handle,0L,1);
which indicates the value of the current data pointer.
PAGE 217
-----------------------------------------------------------------------------
23.6 READING A CHARACTER.
Our previous programs used SCANF to read a character from the keyboard. The
problem with this is that you have to press the <Return> key after every
character. A word processing program would be intolerable under these
conditions. Even a modest application such as controlling the cursor in all
four directions would be difficult.
THE GETCHAR FUNCTION.
The GETCHAR function offers help. This function receives the pressed key's
code immediately. Even here there is a difference between theory and practise.
Almost all other C implementations use this function according to the rules.
Excpet for the AMIGA. The amiga requires the <Return> key for execution.
23.6.1. STANDARD INPUT / OUTPUT.
Usually data is entered through the keyoboard into the computer. Messages and
the results of calculations usually appear on the screen. These two devices
combined are called the console. If you do not instruct the computer to get
the source data from or send the destination to a particular device, it
defaults to the standard input/output (keyboard and screen). The good news
is that these devices can be changed by the user. The Amiga can input data
from another device instead of the keyboard.
STANDARD DEVICES.
The standard input device is the keyboard. The standard output device is the
monitor screen or screen for short. In various windows the output defaults to
the CLI window. This window is the standard output for your previous programs.
All CLI limitations also apply to your program. This causes the error in
GETCHAR.
The CLI is line-orientated. Data processes after you enter your input and
press the <Return> key. Anything can be typed in without the computer
reporting, during input, that this isn't permitted. Press <Ctrl><G> to make
the screen blink. Even though these characters can be entered you cannot
display them on the screen. If you try this the blinking function executes.
The CLI is an input console which processes whole lines and not single keys.
The rule of only using single keys also applies to programs started from the
CLI. Now that you know why <Return> must be pressed, it's time to create a
user defined window to solve this problem.
PAGE 218
-----------------------------------------------------------------------------
23.7 A USER WINDOW.
Creating a personal window is fairly simple. Developing your own window
consists of opening an output file, since the output is written into this
newly created window. The keyboard then acts as the input device according to
the arguments set by the user window.
23.7.1 THE THREE WINDOWS.
You have three device options for opening non-CLI windows :
*
CON:
RAW:
These strings follow the OPEN command in the CLI. They replace the usual
filenames and drive specifiers. The asterisk sends data directly to the
CLI window; a new window isn't created. Nothing has changed for the input
either. Opening an asterisk device only causes the same trouble as before.
The CON: device creates a user window. The OPEN call as listed below creates
an unbuffered OPEN filename and a pathname of CON:
open("CON:0/0/200/50/title line",0,dummy);
The CON: device name replaces the drive specifier and the new windows's
co-ordinates follow. Finally the name TITLE LINE appears in the upper left
corner of the title bar. A slash character (/) separates the window arguments.
The other arguments such a 0 (read) and the dummy value follow the usual
syntax of the OPEN routine.
The returned file handle appears in the examples with all READ calls. An
81-character string acts as a buffer. Try this short routine :
/* window1.c 23.7.1 */
#define c *character
#define ESC 27
main()
{
int dummy = 0, num, handle;
char character[81], line[256];
handle = open("CON:0/0/200/50/My Program",0,dummy);
printf("Opens %d Handle\n",handle);
if (handle!=-1)
{
do
{
num = read(handle,character,1);
printf("Character code >%c< Code %d\n",c,c);
}while (c!=ESC);
close(handle);
}
}
PAGE 220
----------------------------------------------------------------------------
If you forget to test for the successful opening of a file, the computer may
crash using -1 as handle values. When the program executes a new window
appears; nothing happens after you press a key. All keys are processed only
after you press the <Return> key. A new window appears. Unfortunately the
window only works in line mode, like the normal CLI window. Press
<Esc><Return> to exit the program.
The RAW: option also creates a window. The difference between this option and
CON: is the way the information is displayed. A RAW: window displays
information in unfiltered form (i.e. control characters and garbage appear).
Change CON: to RAW: in the above program. Compile ,link and run this new
version and watch what happens next.
Again, a new user window appears. You can size this window and move it around
the screen. Select the new window and sent data to it (type on the keyboard).
Notice that the window reacts to every keypress. However, the input doesnt
appear in the user window. The program seems to be ignoring the RAW:
specification.
All output with printf continues to go to the CLI window. This isn't
surprising since it's the standard output device for programs started from
the CLI. To write something into the user window, for which a special handle
has been issued, a WRITE routine must be used. Instead of printf use
FPRINTF which has the same functions.
Wait a minute. The FPRINTF is a routine for buffered files and an unbuffered
will was opened with OPEN. It only works it the file handle was obtained with
FOPEN. Since the handle is an integer value and not a file pointer, the
handle doesn't print the FPRINTF. However, the library contains a command
named SPRINTF. Instead of writing the prepated data into the output of the
buffer, everything goes to a string. The following example invokes this
function :
char string[200]; /* Not too small */
int test = 4711;
sprintf(string,"The result is %5d\n",test);
PAGE 220
-----------------------------------------------------------------------------
Now the user can write anything into his own window - the PRINTF function
isnt required for output into the CLI window. Now everything needed for
input / output in the new window is available. Here is the corrected
listing :
/* window3.c 23.7 */
#define ESC 27
main()
{
int dummy = 0, num, handle;
char c, line[256];
handle = open("RAW:50/50/200/60/My program",0,dummy);
printf("Handle %d open\n",handle);
if(handle !=-1)
{
do
{
num = read(handle, &c, 1);
write(handle,&c,1); /* output only the character */
sprintf(line,character >%c< Code %d\n",c,c);
write(handle,line,strlen(line));
} while (c!=ESC);
close(handle);
}
}
PAGE 221
-----------------------------------------------------------------------------
23.8 REDIRECTION
The operating system controls the redirection of data so the programmer
doesn't have to worry about it. The use of standard input/output acts as the
condition for ensuring data redirection. This includes familiar functions such
as printf,scanf,putchar,getchar,puts etc....
Look again at the first RAW: program above which used printf for text output.
This example will help you understand redirection.
The "normal" call for programs not expecting arguments is :
program_name
Instructions that are executed by the operating system and not by the program
can follow the filename. The greater than (>) or less than (<) characters
preceed these instructions. The characters inform the operating system that
the standard input/output should be modified. Here's a practical example:
prg <file1 >file2
The program never sees these two arguments. The operating system reads the
standard input from file1 and sends the output to file2. The operating system
also opens and closes the files automatically. The greater than (>) and less
than (<) characters indicate the direction of data, as an arrow would indicate
direction. You can immediately see that the data goes to the filename FILE2.
Let's examine this process using the window2.c RAW program in the previous
section. The PRINTF can be redirected so that all text goes to a file or
printer (PRT:). Start the program (called window2 here) with the following
line :
window2 > output.data
The new window appears again but no keypresses seem to affect the window.
That's all right, since the output which would otherwise appear in the CLI
window now goes to the file OUTPUT.DATA. After typing on the keyboard for a
while press the <Esc> key. The window dissappears and the user returns to CLI.
The OUTPUT.DATA file can now be read using the ED or TYPE commands.
Input can also be redirected from the keyboard to a file. This is how a MAKE
file could be created. In these programming examples this wouldnt make sense
since you dont read from the standard input. A redirection would not make
much sense in this case.
PAGE 222
-----------------------------------------------------------------------------
The standard input / output is a normal line. Just as in opening a file, a
file handle is returned. Since these lines are always open, the programmer
doesnt have to worry about them. There are of course variables for there
handles. They are:
stdin
stdout
stderror
The STDIN variable represents "standard input" and the STDOUT variable
represents "standard output". Whats the third ?
In addition to the input and output of "normal" information C offers an error
channel. This makes sense in the following situation.
As described above, the information is redirected. Because an error occurred
in the input (e.g. a non-existant input file) an error message appears on the
screen. Wait, the standard output was written to an other file. This would
mean that the error message was written to the file and the user might not
know about this error. For this reason error messages use a separate channel
to display user messages.
All three variables (STDIN,STDOUT and STDERROR) can be used as file pointers
which are returned through FOPEN.
CAUTION :
These buffered input / output routines should not be confused with the
unbuffered ones. Permitted functions with the file pointers above would be
fprintf,fwrite,fread etc...
The definition of GETCHAR can be found in the file STDIO.H under :
getc(stdin)
PAGE 223
-----------------------------------------------------------------------------
CHAPTER 24 - TRICKS AND TIPS
During programming unexpected errors can sometime occur. For a novice
programmer, the reasons for these problems can be very hard to find. Much
work is often required to determine whether the source of the problem is in
the source code. the compiler or the operating system. This chapter has a
few hints for helping you find those errors.
You'll find that this chapter contains a number of tips and tricks for C
programming on the Amiga. These tips include the creation of C programs
that are accessible from the workbench preporcessor directives and macros.
24.1 STARTING FROM THE WORKBENCH.
You may have already tried to start you own programs from the Workbench.
Prehaps you wondered why, depsite a full disk, nothing appears in that
drawer's window. The reason for this is that every visible program has an
extra file used for storing the programs information and icon data. Every
program which appears in a workbench window has a file with the extension
of .INFO .
Our C program also need .INFO files before they can be accessed from the
Workbench. Select a suitable icon from the Workbench (the clock or notepad
for example). Any other icon cn be selected for this program. Now enter the
CLI. Copy only the source .info file to an .info file for your file.
Remeber to use the .info extension for both files in this command. The
example below copies the notepad.info file to a new .info file for the file
test_workb:
copy notepad.info test-workb.info
Change test-workb to your own filename, and remeber to keep the .info
extension on both filenames.
Quit the CLI. Click the disk icon containing the target file and .info file
to display the icon. If the icon is covering another icon move it to a free
location in the window. Click once on the icon. Press a <shift> key and
click on a disk icon. Select SNAPSHOT from the special menu to save the new
position.
The following program will tell you how it was accessed, from the workbench
or from the CLI.
PAGE 227
---------------------------------------------------------------------------
/* access.c 24.1 */
#include "stdio.h"
main(argc,argv)
int argc;
char *argv[];
{
int dummy = 0, handle;
char line[256];
if(argc) /* Argument numberr not zero */
{
handle = open("RAW:50/50/200/60/my program",0,dummy);
if(handle !=-1) /* no error on opening */
{
sprintf(line,"started from CLI!\n");
write(handle,line, strlen(line));
while(--argc >= 0)
{
write(handle, *argv, strlen(*argv));
argv++;
}
read(handle,line,1); /* Wait for keypress */
close(handle);
}
else
fprintf(stderr,"\nError in opening window\n");
}
else
{
printf("Started from the Workbench!\nRETURN-KEY\n);
getchar();
}
}
It is important that the programmer knows whether the program started from
workbench or CLI. The argument counter ARG senses this. If arg=0, the user
started the program from the workbench. Consider the possible values in the
CLI. As a minimum argc contains a 1 only when the program name was used
during the call without additional arguments. Otherwise this variable is
incremented by the number of parameters. The 0 is an ideal method of
differentiation between the two calls. The big question "Who cares where
the program started ? ".
Start the program window2 from the previous chapter which opens a window.
The workbench creates an additional, useless window. One window is used,
the other remains empty. There must be an option of sensing a program
started from the workbench or from the CLI.
In the first case you get a window automatically and the standard
input/output is automatically directed to this window. The basic functions
such as scanf and printf can be used and you can still enjoy the use of
your window. In the second case the user must handle his own window and
input/output.
PAGE 228
---------------------------------------------------------------------------
24.2 OTHER PREPROCESSOR DIRECTIVES.
The directives #define and #include should be familiar to you by now.
Preprocessor directives make the programmer's work easier. The most
important are the following :
#undef MACRO.
UNDEFINING:
This is the opposite of #define. This undefines (cancels) the macro
definition. Assume that two #define directives had been used as follows:
#define EOS 0
. . . . .
#define EOS '/0'
Any subsequent program sections use the most recent definition of EOS. The
first assignment is ignored for the moment. This process canbe compared
with local variables which use the same name. Access can only occur to the
last variable (definition) defined. If the definition is reversed with
#undef, for example:
#undef EOS
the defined EOS is still present, but now with the first assignment (0).
PARTIAL COMPILATION :
The following directive permits the compilation of certain portions of a
file, depending on the macro that was defined:
#ifdef MACRO
Should the macro be defined, the following portion is processed by one of
these two preprocessor directives :
#elseif MACRO
#endif MACRO
If the macro is unknown at this point, all lines are skipped up to the
following directives. If an #elseif appears the subsequent source code is
compiled up to the #endif.
These directives are comparable to the C commands:
if()
{
. . . .
}
else
{
. . .
}
PAGE 229
----------------------------------------------------------------------------
The commands themselves have nothing to do with the final code. The
reversal of this makes the following line possible :
#ifndef MACRO
Here the macro cannot be defined, so the part following can be compiled.
Certain parts of a file can be included or left out through the setting of
a #define, without changing the file to a great extent. Where are the
directives used ?
In programs intended for compilation on other compilers or even other
computers, some differences must be considered such as compiler errors. To
achieve an error free compiler run, a DEFINE can indicate the compiler
type. These directives can be found in some header files which come with
the compilers.
PAGE 230
----------------------------------------------------------------------------
24.3 FINDING AND REMOVING ERRORS.
No program, in the initial stages of development, is free of either syntax
or logical errors. The compiler calls these syntactical problems to your
attention. Removing these errors shouldn't be a problem. This section
describes a few of the possible meaning behind these errors.
. Missing semicolon
If the semicolon is not missing in the line indicated, check one or two
lines above the line stated by the compiler and check the level of
parenthesis.
. Braces do not Agree
This error often requires a search of the whole area preceeding the error.
If you omitted a brace somewhere, the following part is added to the
function described up to this point. This in turn leads to mysterious error
messages, for example the missing semicolon in the next function
definition.
. Wrong Answers
If a function which executes flawlessly suddenly returns wrong results, the
error may be caused by a failure to declare the function (not for INT). For
this reason all functions not having a return value of INT should be
declared globally at the beginning of the file. The global declaration
keeps newly added functions, which also use this routine, from erroring
out. Complex formulas which work with various operators and data types
should generally be in parentheses. This not only makes them more visible,
but also prevents errors. Nobody knows all the priorities of various
operators by heart and this can quickly lead to problems if a mistake is
made. It is better to use one parenthesis too many than one too few.
. Easily confused character combinations.
Even experience programmers have misread these characters :
== and =
&& and &
|| and |
Watch for the legal maximum values of the various data types in
calculations. They may differ from one compiler to another.
PAGE 231
----------------------------------------------------------------------------
char -128 to 127
int -32767 to 32766
long -2,147,483,648 to 2,147,483,647
float -10**38 to 10**38
double -10**303 to 10**303
These can never be exceeded, even in intermediate results of a long
equation such as :
i = (x * y * z-z) / y-x;
This can happen quickly with x*y*z if the three factors contain large
numbers.
.System crash
This may have many causes, for example:
Pointer not initialised.
Wrong parameter passed (wrong data type).
Pointer access to old address (except for char pointer).
PAGE 232
----------------------------------------------------------------------------
CHAPTER 25 - SYSTEM PROGRAMMING
One of the reasons people work with the C language is because of it's
speed. The main reason Amiga owners use C is probably because the Amiga's
operating system was written in C. Knowing the peculiarities of C often
helps you understand the Amiga's operating system. This is especially
noticeable during system programming when you try to get more performance
from the computer.
INTUITION:
This chapter takes you into the world of intuition. Intuition is the
section of the operating system that is concerned with windows, screens,
icons, gadgets, menus and the mouse. Intuition's capabilities are so vast
that we can explore only a small portion of this material. Youll find
additional references suggested if you wish to explore further. We chose to
limit ourselves to introductory material on Intuition, especially the
creation of windows and screens.
One small warning in advance: Intuition is very complex. Youll see this
from the C source that follow in this chapter - the source codes are much
more complicated than the ones youve seen so far. Even if you dont
understand everything , you should try the programs and experiment with
them.
25.1 THE INTUITION PRINCIPLE.
To use intuition in use programs, some conventions must be followed. For
example, Intuition routines can only be used once the intuition library has
been opened. Intuition is simply a large library containing the functions
used in connection with windows and similar things. The difference from the
C libraries presented up to now is that these routines are not linked
during linking. The user relies on the fact that they are stored somewhere
in the computer and are available during program execution. This has some
advantaged. Since many programs use the same intuition routines, they dont
have to be stored several times in the memory. Every program can use the
routines for its own purposes, even if they work with other programs in
memory (multitasking). This save working memory. It makes no difference
where these routines are stored in memory. When you open the
INTUITION.LIBRARY, a pointer is returned to the beginning of this function
list. A trademark of intuition programming is a lot of pointers and
structures.
PAGE 235
----------------------------------------------------------------------------
25.2 A WINDOW UNDER INTUITION.
We displayed a window on the screen the the normal OPEN function. This
functions lets the user select the size and position in advance, and
permits window sizing and movement. However, intuition offers special
features for windows. Lets look at the required structure which contains
all the important information for the window:
struct NewWindow
{
SHORT LeftEdge,TopEdge;
SHORT Width, Height;
UBYTE DetailPen, BlockPen;
ULONG IDCMPFlags;
struct Gadget *FirstGadget;
struct Image *CheckMark;
UBYTE *Title;
struct Screen *Screen;
struct BitMap *BitMap;
SHORT MinWidth, MinHeight;
SHORT MaxWidth, MaxHeight;
USHORT Type;
}
THE NEWWINDOW STRUCTURE :
The structure definition can be found in the header file
intuition/intuition.h. Now let's filter out the items that we are really
going to use. The name is already remarkable: NewWindow. The same file
usually contains a structure named Window. You need the NewWindow structure
to define a new user window.
The first four entries represent the upper left corner and the windows
dimensions in height and width. These are the same values as those used in
the CLI's Open command:
open("CON:20/40/200/50/windowtitle",0,0);
To use the same values for the intuition window, the values are assigned to
the structure components. The following definition preceeds the window
description :
struct NewWindow NewWindow;
....
NewWindow.LeftEdge = 20;
NewWindow.TopEdge = 40;
NewWindow.Width = 200;
NewWindow.Height = 50;
PAGE 236
-----------------------------------------------------------------------------
The capabilities a CLI OPEN command cant provide are colour settings for
the window.
The values in DetailPen specify the window title and the height of the
title bar. The number describes the colour register index. If 4 possible
colour are available, they are numbered from 0 to 3. The colors cannot be
changed because they're determined by the settings in preferences. The
background always occupies register 0. The BlockPen register draws the
color for the window's border.
25.2.1 THE WINDOW FLAGS :
The next entry (IDCMPFlags) will be skipped since it isn't used. The
element Flags in the NewWindow structure determine certain items in the
window, which are set with DEFINES. Every define determines whether or not
an intuition function is required (e.g. WINDOWSIZING). Look at the defines
used in the sample program:
There are many more useful defines included for windows:
SMART_REFRESH:
This causes the Amiga to control and store everything concerning window
changes and contents. If another window is dragged over the user window, a
part of window may be temporarily obscured. The computer automatically
stores this area in a buffer and, if needed, will restore it again.
ACTIVATE :
This automatically activates the window once it is opened. This saves the
user the trouble of clicking the window to activate it.
WINDOWSIZING:
Permits a window with a size gadget to change dimensions. By selecting this
DEFINE, the gadget appears in the lower right corner of the window. The
operating system keeps watch over this gadget for any activity.
WINDOWDRAG : The window can be moved.
WINDOWDEPTH:
The window can be moved in front of or behind other windows with the front
and back gadgets (the gadgets in the upper right corner). The operating
system keeps watch over these gagets for any activity.
NOCAREREFRESH :
Practically anything that occurs during program execution can selectivley
generate a message from the operating system. If you want a message
displayed when you try resizing a window, you can generate one. For
example, when editing a text using ED, the text file must be updated when
the window changes size. The program gets the message that the window must
be brought to the current condition. With the DEFINE above, the operating
system is told that no such message is desired. The message is not needed
since SMART_REFRESH takes care of all the update work when active.
PAGE 237
----------------------------------------------------------------------------
Of the remaining elements of the NewWindow structure, only Title and the
last five are of interest. As the name implies the title is placed into the
title bar at the top of the window. The following program demonstrates that
only the address of the string is assigned:
NewWindow.Title = "The user window";
If no constants are used as in the example, the memory space must be
prepared by the user and it's beginning assigned to the entry Title.
The values MinWidth,MinHeight,MaxWidth,MaxHeight indicate the minimum and
maximum values for the user window. The user cannot go beyond these
limitations. Since the maximum height of a window is 200 or 400 pixels,
these numbers are used more frequently. PAL versions of the Amiga can
create a maximum height of 256 or 512 pixels, depending on the mode
selected.
Finally, WBENCHSCREEN is entered into the element TYPE so that the
parameters preset by workbench can be used.
25.2.2 OPENING A WINDOW.
After this preliminary work, the window can finally be opened. The
OpenWindow function opens the window and returns a pointer. This pointer
points to a window structure but shouldnt be confused with NewWindow. It is
more comprehensive than NewWindow and can be examined in INTUITION.H.
The OPENWINDOW function requires the address of the newwindow structure as
the parameter. Since many compilers permit the passing of whole structures,
the following expression can be used to determine the address :
&NewWindow
If everything was processed properly, the new window will have the
specifications which were entered in NewWindow.
The CLOSEWINDOW function is all you need to close the window again (e.g.,
when the program is finished). It has the window pointer as it's only
parameter. The window dissappears again.
Finally the intuition library closes to leave everything the way it was
found. If you close intuition before closing the last window, the Guru
Meditiation appears.
PAGE 238
-----------------------------------------------------------------------------
25.2.3 A WINDOW PROGRAM :
This listing describes a large amount of theory. The program opens a window
which can be moved, sized and moved to the front or back. Watch uppercase
and lowercase letters in this listing.
/* Window intuition.c 25.2.3 */
#include <exec/types.h>
#include <intuition/intuition.h>
extern struct Window *OpenWindow(); /* Delcaration */
extern long *OpenLibrary(); /* Hello Aztec user */
struct IntuitionBase *IntuitionBase;
#define INTUITION_REV 0
main()
{
struct NewWindow NewWindow;
struct Window *Window;
long i;
IntuitionBase = (struct IntuitionBase *)
OpenLibrary("intuition.library",INTUITION_REV);
if(IntuitionBase == NULL)
exit(FALSE);
NewWindow.LeftEdge = 20;
NewWindow.TopEdge = 20;
NewWindow.Width = 200;
NewWindow.Height = 80;
NewWindow.DetailPen = 0;
NewWindow.BlockPen = 2;
NewWindow.IDCMPFlags = NULL;
NewWindow.Flags = SMART_REFRESH | ACTIVATE |
WINDOWSIZING | WINDOWDRAG | WINDOWDEPTH |
NOCAREREFRESH;
NewWindow.FirstGadget = NULL;
NewWindow.CheckMark = NULL;
NewWindow.Title = (UBYTE *)"The User Window";
NewWindow.Screen = NULL;
NewWindow.BitMap = NULL;
NewWindow.MinWidth = 80;
NewWindow.MinHeight = 25;
NewWindow.MaxWidth = 640;
NewWindow.MaxHeight = 200; /* PAL = 256 */
NewWinndow.Type = WBENCHSCREEN;
if((Window= OpenWindow(&NewWindow))==NULL)
exit(FALSE);
for(i=0;i<=800000,i++) /* small pause */
;
CloseWindow(Window);
CloseLibrary(IntuitionBase);
exit(TRUE);
}
PAGE 239
----------------------------------------------------------------------------
The program above exactly follows the indications and requirements
previously stated. First the intuition library opens. If this is not
possible for some reason a 0 is returned as an intuition pointer.
Continuing would make no sense at that point so the program will end. The
window pointer returned can be null so it must be tested.
Examine the declaration of the OPENWINDOW function at the beginning of the
listing. This not only preserves a good C style, but also the suppresses
the changing of returned values with the CAST statement. For example, this
is the case in the OpenLibrary because the routine returns not only the
intuition pointer but also a variety of other pointer types. Nevertheless
the function should be declared at least as a routine which returns
pointers. This prevents, for example, the Aztec system crashes which result
from not declaring the function. This means that, for the compiler, integer
values will be delivered which in Aztec are only 2 bytes long. It should be
obvious that the value with 4 bytes does not arrive. Only 2 bytes will be
accepted.
The CAST statement STRUCT INTUITIONBASE * can be used to trick the best
compiler. Most of the time the resulting value isnt equal to 0 and doesnt
cause the system to crash. During the next access to a library function
with the INTUITIONBASE pointer, the system crashes. This accident cannot
occur with the Lattice C compiler since it's integers always use 4 bytes.
During the opening of the library, the routine wants a version number which
is required for proper processing. If the system has the same or later
version, everything proceeds smoothly. Since this program has no special
needs, a 0 is selected.
While opening the window, the address of the NewWindow structure is passed.
The system then accepts the data in an internal area so that even this
variable, NewWindow is no longer required. If something is done to the
window, it can be done with the window structure.
If the compilation displays warning messages, don't be alarmed. Some
structures need definition which are not used explicitly but appear in a
structure definition as sub-elements. Maybe the functions required can be
included with include. This may also result in additional unknown
structures surfacing which also want to be defined. The only solution is to
define all functions with include. This requires an enormous amount of
memory space, and increases compiler time significantly. Unless the user
has a RAM disk on which all include files are stored, it is not advisable
to do this because nothing changes in the object code anyway.
PAGE 240
----------------------------------------------------------------------------
After the general framework of the window program has been constructed,
some experimentation is helpful. Change a few values in the NewWindow
structure to see the effect on the window. Leave the unknown structure
entries and the type element untouched.
Under intuition, it's impssible to exhaust the topic of windows. The user
who wants to know more should obtain more literature on this subject.
PAGE 241
----------------------------------------------------------------------------
25.3 SCREENS.
A screen is simply a CRT (Cathode Ray Tube) display. In most PCs and home
computers, only one screen shows the screen contents. The Atari ST and
Amiga screens can display several windows at a time.
The Workbench screen is already familiar to you. Any number of windows can
be opened on any screen, depending on the amount of memory available. It
also determines the number of colours available and the colour composition
available to be used by the windows. The workbench generally offers 4
different colours and works with a resolution of 640*400 pixels (640*512
pixels in PAL systems).
The user can specifiy these values for each program in order to construct a
screen to personal taste. The number of colours depends on the number of
available bit-planes. A bit-plane represents a part of memory which is used
for storing graphics. More memory permits more bit-planes and therefore
more colours. The workbench uses two bit-planes for 4 colours. A table
illustrates the connection between colorsa and bitplanes.
Number of bit-planes Number of Colors.
------------------------ -----------------
1 -> 2
2 -> 4
3 -> 8
4 -> 16
5 -> 32 (not always possible).
Not only can the number of colours be selected, but also the resolution of
the screen. You can reach a maximum of 32 colours and a resolution of
640*400 pixels (640*512 in PAL systems). As in the window, two colour
registers canbe assigned, which are responsible for the borders and the
background. Since these values which are stored in the NewScreen structure
stongly remind you of the NewWindow structure, let's look at the structure
definition.
struct NewScreen
{
SHORT LeftEgde,TopEdge,Width,Height,Depth;
UBYTE DetailPen,BlockPen;
USHORT ViewModes;
USHORT Type;
struct TextAttr *Font;
UBYTE *DefaultTitle;
struct Gadgets *Gadgets;
struct BitMap *CustomBitMap;
}
PAGE 242
----------------------------------------------------------------------------
The first entries in this structure have the same names as those in
NewWindow. They also have exactly the same meanings. DEPTH indicates the
number of bit-planes (1-5), which were already discussed. DetailPen and
BlockPen are the colour indices. They depend on the number of available
bit-planes.
The next important entries are TYPE (the CUSTOMSCREEN must be set here) and
DEFAULTTITLE which points to the title line of the screen. This is enough
for the user program to fully define a screen.
The beauty of intuition is that everything follows a certain pattern so
that many different problems can be solved in the same manner. After
understanding how to create a window, it shouldn't be a problem to create a
screen on the monitor. First the NewWcreen structure is stored in the
manner described above. Then the screen is opened with the following
function :
Screen = OpenScreen(&NewScreen);
The variable Screen represents a pointer to the structure named screen.
Also, the structures NewWscreen and Screen must be differentiated here.
NewScreen is only required once for the OpenScreen function. The data is
transferred to the Screen structure (the Screen structure is much more
comprehensive than the NewScreen structure). A pointer to the new screen
structure is the return value.
To open a window to this screen, the initialisation of the NewWindow
structure must be changed slightly. The TYPE in the define WBENCHSCREEN is
replaced by CUSTOMSCREEN. The entry screen must be supplied with a Screen
pointer. The define CUSTOMSCREEN indicates that the window to the user
screen should be opened. The window gets all the capabilities offered by
the screen. Since several screens can be opened by a program, the window
must be attached to a specific screen. This assignment can only be made
after the screen has already been opened and the screen pointer is
available.
Before the program ends, the screen is closed is closed with CloseScreen to
which the screen pointer is passed. The window must be closed before the
screen, or serious problems will occur.
PAGE 243
-----------------------------------------------------------------------------
The listing for the subject of screens has some more enhancements which
will be explained now :
/* Screen_intuition.c 25.3.1 */
#include <exec/types.h>
#include <intuition/intuition.h>
extern LONG OpenLibrary();
extern struct Screen *OpenScreen();
extern struct Window *OpenWindow();
struct IntuitionBase *IntuitionBase;
#define INTUITION_REV 0
struct NewScreen NewScreen =
{
0,0,
640, /* width */
200, /* height */
3, /* 3 bitplanes = 8 colours */
3,5, /* Another colour combination */
HIRES,
CUSTOMSCREEN,
NULL,
"To end the program, please click Close Gadget!",
NULL,
NULL,
};
struct NewWindow NewWindow =
{
40,40, /* X and Y position */
280,120, /* Width, Height */
4,6, /* Colours 0 - 7 */
CLOSEWINDOW,
WINDOWCLOSE | SMART_REFRESH | ACTIVATE | WINDOWSIZING |
SIZEBRIGHT | WINDOWDRAG | WINDOWDEPTH,
NULL,
NULL,
"**** HELLO ****",
NULL,
NULL,
190,20,
640,200, /* IN PAL CHANGE 200 TO 256 */
CUSTOMSCREEN
};
main()
{
struct Screen *Screen;
struct Window *Window;
if((IntuitionBase = (struct IntuitionBase *)
OpenLibrary("intuition.library",INTUITION_REV))==NULL)
exit(FALSE);
if((Screen = OpenScreen(&NewScreen))==NULL)
exit(FALSE);
NewWindow.Screen = Screen; /* DO NOT FORGET THIS !!!! */
if((Window = OpenWindow(&NewWindow))==NULL)
exit(FALSE);
/* Wait for Close Gadget */
Wait(1 << Window->UserPort->mp_SigBit);
printf("\nLast window Values: %d/%d/%d/%d\n\n",
Window->LeftEdge,
Window->TopEdge,
Window->Width,
Window->Height);
CloseWindow(Window); /* Close Everything in sequence */
CloseScreen(Screen);
CloseLibrary(IntuitionBase);
exit(TRUE);
}
PAGE 244
---------------------------------------------------------------------------
Since C programmers are usually too lazy to type, structure initialisation
is best performed during the definition of the variables. Another
innovation is the entry of the CLOSEWINDOW in the IDCMPFlags. In
combination with the WINDOWCLOSE in Flags, the close gadget can be tested.
The WAIT statement tells the system to wait for the activities entered in
the IDCMP flags:
Wait( 1 << Window->UserPort->mp_SigBit);
Clicking the close gadget is the only way out. As in the window program all
flags are set, which permit the user to change the size and position of
the window. The window pointer points to the desired elements LeftEdge,
TopEdge, Width and Height.
Although the example program above uses three bit-planes, the maximum
accessible colours is 8. Therefore, the colour registers can be from 0 to
7. Experimenting with screens takes up a lot of memory. The program above
requires almost 80K or RAM.
PAGE 245
---------------------------------------------------------------------------
25.4 TEXT/GRAPHIC WINDOW DISPLAY.
Intuition sees little difference between processing text or graphics. Since
a printf call doesn't work in intuition windows something else must be
used.
25.4.1 TEXT:
The TEXT function handles string output. A pointer to a RastPort structure
passes the text to the window structure. The user doesnt have to know the
appearance of RastPort or what function it performs. It is enough to passs
the expression to the responsible routine :
Window->RPort
TEXT requires a character string and it's length as additional parameters.
The format is as follows :
Text(Window->RPort,string,length);
25.4.2 MOVE:
The string passed by text appears at the current cursor position. The MOVE
function sets this position using this syntax :
Move(Window->RPort, xpos, ypos);
Before each call of the TEXT function, MOVE should position the cursor. A
small routine for this task follows:
text(w_ptr,s,x,y)
struct Window *w_ptr;
char *s;
int x,y;
{
Move(w_ptr->RPort, x, y);
Text(w_ptr->RPort, s, strlen(s));
}
PAGE 246
----------------------------------------------------------------------------
To keep the function generic, the pointer to the window in which the text
should appear is passed. Because of this you can service several windows
with the same function. A call appears as follows:
text(Window, "Attention!", 20, 40);
The text appears at position (20/40), if the window will allow it (the IF
is important). You can write a much text as you wish in the window.
Intuition ensures that no window or screen is overwritten. If the text
can't be displayed completely in the window, the writing stops at the
window's right border. The user can be assured that nothing is accidentally
drawn in other windows.
25.4.3 DRAW :
The DRAW function draws lines. The parameters of the routine are:
Draw(Window->RPort, x, y);
Sonething's missing here- you need two points to draw a line. With Draw,
the straight line os drawn between the current position and the (x/y)
point. The DRAW function belongs to the graphics.library instead of
intuition.library. First this library must be opened then it returns a
special pointer.
For drawing, the mouse co-ordinates are normally needed. They are found in
mouseX and mouseY which are two elements of the window structure. This
includes everything needed to write into a window.
In the following program argv and argc re-appear. These other values can be
used from the CLI rather than the preset values. The call has the following
format :
prg X-RES Y-RES BITPLANES
for example
draw 640 200 3
It is interesting here that the resolution of a screen can be larger than
the maximum resolution of the display monitor. 640*200 pixels (640*256) can
be represented, but if 800 dots are selected in the horizontal axis, the
window can be shifted beyond the right screen border. The same is true of
the vertical axis.
PAGE 247
----------------------------------------------------------------------------
25.4.4 SMALL DRAWING PROGRAM:
/* draw.c 25.4.3 */
#include <exec/types.h>
#include <intuition/intuition.h>
extern LONG OpenLibrary():
extern struct Screen *OpenScreen();
extern struct Window *OpenWindow();
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
#define INTUITION_REV 0
#define GRAPHICS_REV 0
struct TextAttr Font =
{
"topaz.font",
TOPAZ_SIXTY,
FS_NORMAL,
FPF_ROMFONT,
};
UBYTE screentitle[81];
struct NewScreen NewScreen =
{
0,0,
640, /* Width */
200, /* Height - change to 256 for PAL */
3, /* 3 bitplanes = 8 colours */
2,3, /* Another colour combination */
HIRES,
CUSTOMSCREEN,
&Font,
screentitle,
NULL,
NULL,
}:
struct NewWindow NewWindow =
{
20,20, /* x and y position */
400,180, /* Width, height */
0,1, /* Colors (0-7) */
CLOSEWINDOW,
WINDOWCLOSE | SMART_REFRESH | ACTIVATE | WINDOWSIZING |
SIZEBRIGHT | WINDOWDRAG | WINDOWDEPTH,
NULL,
NULL,
" * MY WINDOW * ",
NULL,
NULL,
190,20,
640,200, /* IN PAL CHANGE 200 TO 256 */
CUSTOMSCREEN
};
PAGE 248
----------------------------------------------------------------------------
main(argc,argv)
int argc;
char *argv[];
{
struct Screen *Screen;
struct Window *Window;
register char s[81];
int color = 4;
register int x,y,xalt,yalt;
if((IntuitionBase = (struct IntuitionBase *)
OpenLibrary("intuition.library",INTUITION_REV))==NULL)
exit(FALSE);
if(argc != 4)
{
printf("Error in arguments\n");
printf(" X-Res Y-Res Bitplanes\n");
}
else
}
NewScreen.Width = atoi(argv[1]);
NewScreen.Height = atoi(argv[2]);
NewScreen.Depth = atoi(argv[3]);
if(NewScreen.Depth > 4 || NewScreen.Depth < 1)
NewScreen.Depth = 2;
color = 1 << NewScreen.Depth;
NewScreen.DetailPen = color - 1;
NewScreen.BlockPen = color - 2;
}
sprinf(screentitle,"This screen has %d colours",color);
if((Screen = OpenScreen(&NewScreen))==NULL)
exit(FALSE);
NewWindow.Screen = Screen ; /* Do Not Forget This !!!!! */
PAGE 250
----------------------------------------------------------------------------
if(argc == 4)
{
NewWindow.Width = Screen->Width/2;
NewWindow.Height = Screen->Height/3;
NewWindow.MinWidth = Screen->Width/3;
NewWindow.MinHeight = Screen->Height/5;
NewWindow.Maxwidth = Screen->Width;
NewWindow.MaxHeigth = Screen->Height;
}
if((Window = OpenWindow(&NewWindow))==NULL)
exit(FALSE);
text(Window,"Hello There!",20,20);
/* Initialise with start values */
Move(Window->RPort, xalt = Window->MouseX,
yalt = Window->MouseY);
/* Drawing starts here until upper or left border */
/* is reached. */
while((x = Window->Mousex) > 0 && (y = Window->MouseY > 0)
{
sprintf(s,"x = %3d, y = %3d",x,y);
text(window,s,150,7);
Move(Window->RPort,xalt,yalt);
Draw(Window->RPort,xalt = x, yalt = y);
}
text(Window,"Please click the Close Gadget ",20,20);
/* wait for close gadget */
Wait(1 << Window->UserPort->mp_SigBit);
CloseWindow(Window);
CloseScreen(Screen);
CloseLibrary(GfxBase);
CloseLibrary(IntuitionBase);
exit(TRUE);
}
text(w_ptr, s, x, y)
struct Window *w_ptr;
char *s;
int x, y;
{
Move(w_ptr->RPort, x, y);
Text(w_ptr->RPort, s , strlen(s));
}
PAGE 250
----------------------------------------------------------------------------
Notice the new structure TextAttr with the variable Font. Every NewScreen
structure has, among other things, a component named Font. Here the
character set to be used can be stored (address of the structure). Of
course this is again a pointer to another structure, TextAttr.
The structure for the definition of a character set is very simple. First
there is the name of the font. Then the height of the character amd the
manner of presentation. Finally a flag marks the location of the character
set. We used the character set built into ROM. This font is used in the 60
character screen setting. Eighty characters can be displayed if you use
TOPAZ-EIGHTY instead of TOPAZ_SIXTY.
All entries of NewWindow and NewScreen are alreay initialised, but if the
user wants to use other values for resolution and bit-planes they can be
accepted. The maximum and minimum size of the windows must also be
adjusted. Since the colour indices of the window are always ready for use
with 0 and 1, no conversion has to be performed. The screen, on the other
hand, gets the last two colour registers which if course depend on the
number of colors. The program permits a maximum of 16 colours, which equals
4 bit-planes in the high resolution mode. The title of the screen displays
this information. For this reason an additional variable must be used
because the structure has made no provisions for storing the title.
As soon as the window is opened the text appears. The mouse can be used to
draw something. As long as the mouse pointer doesn't move beyond the upper
or left border of the window, a line will be drawn. The mouse position is
always indicated in the title line. This is not absolute, but relative to
the upper left corner of the window. For this reason a negative value is
possible when the mouse is pushed beyond the left or upper border. The
program uses this as an end criterion.
Because of the output of the mouse coordinates, the current character
position in xalt and yalt must be stored in intermediate storage. Before
the drawing of the line, the values are restored again with the MOVE
function. We have said enough about this program.
However, we have some additional suggestions. Up to now only the define
HIRES was used for user defined screens in ViewModes, which achieves a
horizontal resolution of up to 640 pixels. The Amiga can also produce a
lower resolution which fills a complete display line with 320 pixels. With
the same number of colours only half the memory space is required. The
possibility of working with 5 bit-planes and therefore 32 colours is
created. The only thing required for this is to replace HIRES with NULL.
Try this on the first screen program. Besides the change mentioned above
only the width of the screen must be set to 320 pixels. If 5 bit-planes
were requested, 32 colour are available in colour registers 0-31.
PAGE 251
---------------------------------------------------------------------------
25.4.5 LOW RESOLUTION AND INTERLACE MODES.
The interlace mode can be switched on in ViewModes. The display gets a
vertical resolution of 400 (PAL systems - 512) instead of 200 (PAL - 256)
pixels. Not bad, but here comes the big "but!". This process can only be
realised by lowering the refresh frequency of the monitor from 50 to 25 Hz.
A flickering image occurs on the Amiga monitor. This mode was only intended
for those monitors which had a long screen refresh rate and therefore don't
create flickering. Examine the display and form your own judgement. The
define LACE in the ViewModes enters interlace mode. Some Examples:
Low Resolution (320 pixels):
NewScreen.ViewModes = NULL;
High Resolution (640 pixels) and interlace (400 pixels):
NewScreen.ViewModes = HIRES | LACE;
Low Resolution (320 pixels) and interlace (400 pixels):
NewScreen.ViewModes = LACE;
With 32 colours, a function which permits changing the colour of the pen
would be useful. This input changes the current colour in the colour
register :
SetAPen(Window->RPort,color);
To see this routine in action, a new program must be written. Add the
following lines to the drawing program. First at the beginning of the main
function, add the definition of a variable named colour:
register int, x, y, xalt, yalt, colour = 1;
At the end of the while loop add two additional lines. Here is the complete
loop :
while((x = Window->mouseX) > 0 &&
(y = Window->mouseY) > 0)
{
sprintf(s,"X = %3d, Y = %3d", x, y);
text(Window,s,150,7);
Move(Window->RPort,xalt,yalt);
Draw(Window->RPort,xalt = x,yalt = y);
SetAPen(Window->RPort, colour++); /* New! */
if(colour == color) color = 1; /* New! */
}
PAGE 252
---------------------------------------------------------------------------
This program is on the optional disk as draw16.c. In the beginning the
variable colour is set to 1 to draw the first colour index ( 0 is the
background colour). After each small line fragment, the pen color changes
until the last index (number of colors - 1) has been reached. Then it
starts over. This produces all the colours for the first run, including the
registers not normally used. To make it even more colourful than the 16
colors in the HIRES mode, change the safety test:
OLD: if(NewScreen.Depth > 4 || NewScreen.Depth < 1)
NewScreen.Depth = 2;
to
NEW: if(NewScreen.Depth > 5 || NewScreen.Depth < 1)
NewScreen.Depth = 2;
and change the HIRES to NULL. Consider the reduced Z resolution of 320
pixels instead of 640 pixels and change the program accordingly. This
permits drawing with 32 colours in one window. This program is on the
optional disk as draw32.c. An example call of the program for 32 colours:
draw 320 200 5
Since many colors and high resolution requires a large amount of memory,
here are some examples of memory usage by windows and screens.
Low resolution, interlace mode, 32 colors:
320 (pixels) * 400 (pixels) * 5 (bitplanes) / 8 (bits per byte)
= 80,000 bytes = 78k (PAL 320*512*5/8 = 102,400)
High resolution, interlace mode, 8 colours:
640 (pixels) * 400 (pixels) * 3 (bitplanes) / 8 (bits per byte)
= 96,000 bytes = 93k (PAL 640*512*3/8) = 122,880)
Low resolution, 2 colours:
320 (pixels) * 200 (pixels) * 1 (bitplanes) / 8 (bits per byte)
= 8,000 bytes = 7.8k (PAL 320*256*1/8) = 10,240)
25.4.6 PIXEL PROCESSING:
Other graphics commands besides Draw are :
ReadPixel(Window->RPort, x, y);
WritePixel(Window->RPort, x, y);
PAGE 253
----------------------------------------------------------------------------
READPIXEL :
READPIXEL tests whether the indicated position of a pixel is set and
returns the colour value of the pixel. If no pixel was visible, because the
pixel has the same colour as the background, ReadPixel returns a 0 to the
register number. Is the pixel is outside the window whose port is passed,
the result is -1.
WRITEPIXEL:
WRITEPIXEL sets a single pixel at the position indicated. The colour used
depends on the current colour as in all other routines and is determined
byt SetAPen.
The following is a program which changes the title line of the window used
in a sine format :
/* pixel.c 25.4.6 */
#include <exec/types.h>
#include <intuition/intuition.h>
extern struct Window *OpenWindow();
extern long *OpenLibrary(); /* Hello Aztec User */
extern double sin();
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
#define INTUITION_REV 0
#define GRAPHICS_REV 0
/* Number of colours of workbench */
#define WB_COLORS 4
struct NewWindow NewWindow =
{
10,50, /* X and Y position */
360,120, /* Width, Height */
3,2, /* Colour indexes */
NULL,
SMART_REFRESH | ACTIVATE | WINDOWDRAG | WINDOWDEPTH,
NULL,
NULL,
"This line is Changed! ",
NULL,
NULL,
0,0,
640,200, /* PAL users - change 200 to 256 */
WBENCHSCREEN
};
PAGE 254
--------------------------------------------------------------------------
main()
{
struct Window *Window;
register struct RastPort *r;
register int i, j, top, yoffset;
int i_to, j_to, color, colors[512];
double factor;
if((IntuitionBase = (struct IntuitionBase *)
OpenLibrary("intuition.library",INTUITION_REV))==NULL)
exit(FALSE);
if((GfxBase = (struct GfxBase *)
OpenLibrary("graphics.library",GRAPHICS_REV))==NULL)
exit(FALSE);
if((Window = OpenWindow(&NewWindow)) == NULL)
exit(FALSE);
r = Window->RPort;
top = Window->Height / 4 ;
factor = 2 * 3.1415926 / Window->Width * 1.5;
/* 1.5 sine waves */
for( i = 2, i_to = Window->Width - 2; i < i_to;i++)
{
for(j = 0;j < top; j++)
{
/* Transfer to Array */
color = ReadPixel(r, i, j);
if(++color = WB_COLORS)
colors[j] = 0;
else
colors[j] = color;
/* Increase color index by one */
}
for(j = 0, yoffset = top + top * sin(factor * i)+16;
j < top; j++)
if(colors[j]) /* if pixel should be set */
{
SetAPen(r, colors[j]);
WritePixel(r,i,j + yoffset);
}
}
Delay(1500); /* Wait 1500 ticks = 30 seconds */
CloseWindow(Window);
CloseLibrary(GfxBase);
CloseLibrary(IntuitionBase);
exit(TRUE);
}
PAGE 255
----------------------------------------------------------------------------
LATTICE:
The library for mathematical functions and floating point numbers must be
linked with the standard library. Example :
lc -lm math2
AZTEC:
If you work with the Aztec compiler, the library for mathematical functions
and floating point numbers must be linked with the standard library C.LIB.
Example:
cc +L math2.c
ln math2.o -lm -lc
HOW THE PROGRAM WORKS :
The program runs under the workbench screen and it uses it's colours. Two
bit-planes (four colours) are the default. If the user constructed a
workbench screen which deviates from this, the define WB_COLORS must be
adjusted accordingly. The window which was defined in the NewWindow
structre contains only front and back gadgets. The size cannot be changed.
In a large loop which processes the complete width of the window, all
pixels belonging to one X position are gathered in one array. Before the
value which is returned from the ReadPixel is stored, a colour
transformation is made. Every pixel gets the colour from the following
register. The area of the display which is transmitted, is the upper
quarter of the window. To prevent mix-ups between the information to be
read and written, a complete column is first saved into the array COLORS.
Then the new position of the pixels is calculated using the SIN (sine)
function. The dots are written in the new color with WritePixel at the new
position. The pixels which are the same colour as the background are taken
out. This is tested first to increase the speed. The program will display
the top line a sine wave.
After complete transformation, the Delay program slightly delays the end of
the program to give the user to opportunity to view the window again. The
parameter provides the waiting time in 1/50 second ticks. To achieve a
delay of 30 seconds 1500 ticks must be stored.
PAGE 256
---------------------------------------------------------------------------
25.5 DOS
Besides intuition, DOS (the Disk Operating System) is usually required when
programming. Many programs wouldn't work without the help of AmigaDos. The
DOS routines handle file deletion and renaming, as well as directory
creation and directory display.
DOS is also stored in a library file (dos.library). Unlike the other
libraries dos.library is always open to the user. It doesn't have to be
opened or closed by the user. It is as simple to use as the functions from
the standard library. For example, deleting a file:
result = DeleteFile(filename);
The return value is an integer number which indicates an error if zero and
the correct processing with a number unequal to zero. Complete directories
can be deleted with this function. No files may be contained in the
directory. They must be erased in advanced. The RENAME function is just as
easy to use for renaming files:
result = Rename(old_name, new_name);
The result is also a value unequal to zero. The other parameters old_name
and new_name are just like the filename strings which contain a valid
filename.
PAGE 257
----------------------------------------------------------------------------
25.6 SETCOMMENT.
One routine permits the attachment of a comment to a file. This comment is
completely independant of the content and size of the file and is stored in
the same place as the filename and it's parameters. The DOS command
FILENOTE in the C: directory can attach a comment text to an existing file.
This can also be done with the function:
result = SetComment(filename, comment);
A short program shows this routine in action:
/* Makecom.c 25.6 */
#include <libraries/dos.h>
main(argc,argv)
int argc;
char *argv[];
{
if(argc == 3)
{
if(!SetComment(argv[1], argv[2]))
printf("Error %d\n",IoErr());
}
else
{
printf("Format : MAKECOM FILE COMMENT \n");
exit(TRUE);
}
The filename and the comment passes to the program through the command
line. As in all data transfers, the use of spaces in the actual comment is
not permitted. Here is a sample call:
'makecom makecom.c This-is-a-comment-use-the-list-command-to-display-it'
PAGE 258
----------------------------------------------------------------------------
25.7 READ DIRECTORY:
Many programs that handle files should have a routine for reading disk
directories. To make this possible in a program various functions are
required.
First there is the LOCK function. LOCK locks the specified directory for
access. Only after the LOCK can other functions operate on the directory.
The name of the directory and the access mode are passed to LOCK. Finally
an integer value for READ is passed with the define ACCESS_READ. The
returned key permits the processing of this one directory, similar to a
handle for file accesses or a window pointer under intuition. If this key
is equal to zero an error has occurred.
Two functions are required for reading a directory. One is EXAMINE, the
other EXNEXT. First EXAMINE must be called to obtain the first entry of the
directory. Then a call for EXNEXT must follow for each additional file or
directory. Both routines require both LOCK and a pointer to the
FILEINFOBLOCK structure. This structure contains all important file data.
result = Examine(lock, &fileinfo);
And here is the structure definition:
struct FileInfoBlock {
LONG fib_DiskKey;
LONG fib_DirEntryType;
char fib_FileName[108];
LONG fib_Protection;
LONG fib_EntryType;
LONG fib_Size;
LONG fib_NumBlocks;
struct DateStamp fib_Date;
char fib_Comment[116];
}
The fib_DirEntryType function indicates whether the data currently read is
a normal file (<0) or a directory (>0).
The fib_FileName function contains the name which can be up to 30
characters long, even though it was generously defined here a 108
characters.
The fib_Protection function contains flags which indicate whether the file
can be read, written , executed or erased. The variable is defined as LONG
(32 bits) but only the lower 4 bits are required.
PAGE 259
----------------------------------------------------------------------------
The priorities for these files are as follows:
R W E D
8 4 2 1
R=Read W = Write E = Execute D = Delete
For every protection action one of the above bits must be set. For example
if a file or directory can only be read or erased, the flags W and E must
be set:
RWED
0110 (bits) = 4 + 2 = 6
This variable must contain the value 6. It is important to set the flag
whose function is forbidden. To make changes please use the PROTECT command
from the CLI. The flags which are passed with this program are changed in
such a manner that the functions can be performed. This is exactly the
opposite of their use in the user program. To protect a file from erasing
the following line is required:
PROTECT ED RWE
The fib_Size function defines the file size in bytes.
The fib_NumBlocks function contains the number of occupied blocks on the
disk.
The fib_Date function contains the data when the file was last written.
The fib_Comment function contains the comments for the file.
You now have all the information needed to construct the final program. For
the sake of simplicity we use parameter passing with the command line in
the version. The parameter indicates the directory which should be read.
PAGE 260
----------------------------------------------------------------------------
/* Read_dir.c 25.7 */
#include <libraries/dos.h>
struct FileInfoBlock fi;
main(argc,argv)
int argc;
char *argv[];
{
long lock;
int error;
char filepath[100];
if(argc == 2) /* Parameter Present ? */
strcpy(filepath, argv[1]);
else
strcpy(filepath,"sys:");
lock = Lock(filepath,ACCESS_READ);
printf("Lock value %d\n", lock);
if(!lock)
{
printf("No Lock! ERROR!\n");
exit(FALSE);
}
if(Examine(lock,&fi)) /* First call successful ? */
do
output(); /* return value not of interest now */
while(ExNext(lock,&fi)); /* Until error occurs */
error = IoErr(); /* What error ? */
if(error!= ERROR_NO_MORE_ENTRIES) /* "real" error */
printf("Error %d occured!\n",error);
exit(TRUE);
}
output()
{
if(!*fi.fib_FileName) /* strlen = 0 */
{
printf("Empty!\n");
/* for example root-directory of RAM disk */
return(0); /* That's directory without name */
}
if(fi.fib_DirEntryType > 0)
printf("Directory Name");
else
printf("Filename ");
printf(": >%20s< RWXD %lx bytes: %-6ld Block %-4ld\n",
fi.fib_Filename, fi.fib_Protection,
fi.fib_Size, fi.fib_NumBlocks);
if(*fi.fib_Comment) /* if comment present output it ! */
printf("Comment: >%s< \n", fi.fib_Comment);
return(fi.fib_DirEntryType > 0); /* Return file type */
}
The protection flags are not decoded separately in this program (it wouldnt
be a problem to do that), but are presented as a hexadecimal number (the
%lx format instruction). You can enhance this program if you wish.
The flags can be displayed in RWED format using the LISt command from the
CLI. With PROTECT any flag can be set and with list the result can be
observed. This information can be compared with the results from the user
program.
PAGE 261
----------------------------------------------------------------------------
25.8 CONCLUSION.
You now have the general knowledge needed to write simple programs in the C
language. As you take time to develop your own programs, functions and
libraries, keep this book nearby for reference. Since it's difficult to
memorize everything about a language, this book will help you with the
complex concepts of the C language.
You may be wondering why we didn't spend more time with the aspects of the
Amiga. We admit that we didn't include as much about the operating system
and Intuition as we would have liked. However, these are difficult concepts
for a beginner to understand, and we felt it best to just give the reader a
few general examples controlling these areas in C. Since C is a
transportable language, you may prefer to write transportable source codes.
If you want to continue your education in C, we recommend the Abacus book
AMIGA C FOR ADVANCED PROGRAMMERS (*-*-*** LOOK OUT FOR THIS TITLE ON FUTURE
ALLIANCE DOX DISKS - RAZOR BLADE *-*-***). This book coovers subjects that
interest professional Amiga programmers: Combining assembly language and
source codes; debugging (finding errors); jump table and more. In addition,
AMIGA C for advanced programmers details Intuition programming in C (menus
, requesters etc..)
We wish you luck in your future as a C programmer.
*-*-*-* Alliance *-*-*-*
PAGE 262
-----------------------------------------------------------------------------
APPENDICES
APPENDIX A - FUNCTIONS.
/* */
/* NAME : STRLEN */
/* PARAMETER: S (STRING) */
/* RETURN VALUE: LENGTH (INT) */
/* FUNCTION: RETURN NUMBER OF CHARACTERS IN "S" */
/* OTHER: */
strlen(s)
char s[];
{
register int i = 0;
while(s[i])
i++;
return(i)
}
/* */
/* Name : strcpy */
/* Parameter: s (string) , t (string) */
/* return value: - */
/* Function: Copies "s" to "t" */
/* Other: */
strcpy(t,s)
register char *t, *s;
{
while(*t++ = *s++)
;
}
/* */
/* Name: strcat */
/* Parameter: s (string) , t (string) */
/* Return Value : - */
/* Function: attach "t" to "s" */
/* Other: */
strcat(s,t)
register char *s, *t;
{
while(*s)
s++;
while(*s++ = *t++);
}
PAGE 265
----------------------------------------------------------------------------
/* */
/* Name: letter */
/* Parameter: z (char) */
/* Return value: If a letter (1) else (0) */
/* Function: Determine if it was a letter or not. */
/* Other: - */
#define FALSE 0
#define TRUE 1
letter(z)
register char z;
{
if((z>='a' && z <='z') || (z >='A' && z <='Z'))
return(TRUE);
return(FALSE);
}
/* */
/* Name: c_comp */
/* Parameter: c1 (char) , c2 (char) */
/* REturn Value: 1 (TRUE), 0 (FALSE) */
/* Function: Compares two characters. */
/* Other : requires two characters () */
#define FALSE 0
#define TRUE 1
extern int grklflag;
c_comp(c1,c2)
register char c1,c2;
{
if (c1 == c2) return(TRUE);
if(grklflag && letter(c1) && letter(c2))
if((c1 + 'a' - 'A' == c2) || (c2 + 'a' - 'A' = c1))
return(TRUE);
return(FALSE);
}
PAGE 266
----------------------------------------------------------------------------
/* */
/* Name: strcmp */
/* Parameter: s (string) , t (string) */
/* Return Value: identical 0 not identical 1 */
/* Function: Compares "s" and "t" */
/* Other: - */
strcmp(s,t)
register char *s, *t;
{
register int identical;
while(identical = c_comp(*s,*t++))
if(!*s++)
return(0);
return(!identical);
}
/* */
/* Name: strchar */
/* Parameter: s (string). c (char) */
/* Return Value: Position (int) or -1 */
/* Function: Determines position of char "c" in "s" */
/* Other: - */
strchar(s,c)
register char s[];
register char c;
{
register int i = 0;
while(!c_comp(s[i],c) && s[i])
i++;
if(s[i]) return(i);
return(-1);
}
/* */
/* Name: strchback */
/* Parameter: s (string) , c (char) */
/* Return Value: Index (int) */
/* Function: Searches for Position of char "c" in "s" */
/* Other: requires c_comp(), strlen() */
strchback(s,c)
register char s[];
register char c;
{
register int i = strlen(s);
while((i >= 0) && !c_comp(s[i],c))
i--;
return(i); /* Error = -1 */
}
PAGE 267
----------------------------------------------------------------------------
/* */
/* Name: ltoa */
/* Parameter: n (long), s (string) */
/* Return Value - */
/* Function: Converts long value to a char string */
/* Other: Requires reverse. */
#define TRUE 1
#define EOS '\0'
itoa(n, s)
register char s[];
register long n;
{
register int i = 0;
register int forechar = 0;
if(n<0)
{
forechar = TRUE;
n = -n;
}
do
{
s[i++] = n % 10 + '0';
} while((n /= 10) > 0);
if(forechar)
s[i++] = '-';
s[i] = EOS
reverse(s);
}
/* */
/* Name: itoa */
/* Parameter: n (int), s (string) */
/* Return value: - */
/* Function: Converts integer string to char value. */
/* Other: requires ltoa() */
itoa(n, s)
register int n;
register char s[];
{
ltoa((long) (n),s);
}
PAGE 268
----------------------------------------------------------------------------
/* */
/* Name: atol */
/* Parameter: s (string) */
/* Return Value: n (long) */
/* Function: Converts char string into long value. */
/* Other: */
long atol(s)
register char *s;
{
register long val;
register int sign = 1;
while(*s == ' ')
s++;
if(*s== '+' || *s == '-')
sign = (*s++ == '+') ? 1 : -1;
for(val = 0; *s >= '0' && *s <= '9'; ++s)
val = 10 * val + *s - '0';
return(sign * val);
}
/* */
/* Name: atoi */
/* Parameter: s (string) */
/* Return Value: Integer number */
/* Function: Converts char string into Integer. */
/* Other: Requires atol. */
atoi(s)
register char *s;
{
long atol();
return(atol(s));
}
PAGE 269
-----------------------------------------------------------------------------
APPENDIX B - THE HISTORY OF C.
C originated from BCPL (Basic Cambridge Programming Language). The B
language came from BCPL and C came from the B language. C was developed in
the mud-seventies by Dennis M Ritchie, who, at that time, was working for
Bell laboratories.
C was originally intended for developing an operating system which, among
other things, would be capable of multi-user and multitasking execution,
namely UNIX. This explains why C programs are so fast. Multitasking
procedures require a very fast operating system which up to then could only
be written in assembly language. Dennis Ritchie developed the C language to
circumvent the error prone and unclear assembly language programming. The
result, the UNIX operating system, consists of about 13,000 lines of which
only a minimum of about 800 lines were written in assembly language. The
rest of the operating system is in C.
C became popular with the introduction of the Amiga and the Atari ST, whose
operating systems were written in C. The Amiga's intuition user interface
was written almost completely in C. Professional programmers and software
houses prefer using C to develop new programming projects. C has another
advantage: it is portable. This means that C programs can, theoretically,
be transferred to other computers and compiled there without changes.
The reason for this is that C has a small number of commands available to
all compilers. Parts which are computer specific such as input and output
don't belong to the actual C language. These routines are delivered with
the language in libraries adapted to the peculiarities of that particular
computer. The C programmer doesn't have to be concerned about this. He
knows that the GETCHAR function gets a character from the keyboard
regardless of whether the progran is executing on a C64, an IBM PC, an
Amiga or an Atari ST. This portability means less programming for the
developers - just transfer the program over to another computer, make the
changes needed for the new computer and recompile it.
HOW A C COMPILER WORKS:
Every C compiler has been split into various program portions which,
depending on the manufacturer, are available either in a program module or
in several smaller programs.
The first part of a C compiler is the preprocessor; this only replaces one
text portion with another according to the user's commands. The result of
this effort is a file containing pure text which can be processed with the
editor. The result passses to the scanner which searches for command words
specific to C. It recognises these words and stored them in abbreviated
form. In this format the command is stored as a token (code) instead of as
individual letters. Tokenizing takes up less memory and accelerates the
translation.
PAGE 270
----------------------------------------------------------------------------
PARSER:
After completeing this test run, the parser appears. If tests the source
code commands for the correct syntax, and differentiates between correct
and incorrect combinations of C commands. The parser knows all the rules
about C syntax. Just as in everyday conversation, string words together
isn't enough. The parser ensures that the expression is correct.
As the lasst part of the actual C compiler, the code generator converts the
text processed by the parser into machine language commands. Some C
compilers first translate the machine language commands into assembly
language so the programmer can streamline the generated code. This is
really unnecessary since the C compilers on the market already produce very
efficient machine language code. After completing this run, the compiler
saves the object code to disk with the extension of .o.
The final process is linking the object files with the required libraries
to produce an executable program. The linker is used for this purpose.
PAGE 271
----------------------------------------------------------------------------
APPENDIX C - THE LATTICE C COMPILER.
C compiler manufacturers are constantly updating and improving their
compilers. For instruction on how to install the compiler, see the
documentation and any README files that came with the compiler.
The C compiler is called with lc and the source filename. As a minimum the
following is required:
lc hello
With the -L option, the linker can be loaded immediately after the call of
the C compiler. The linke (here BLINK) can also be called independantly.
This is a standard call to compile and link a source code named math2.c
into a program named math2.
When the library for mathematical functions and floating point numbers must
be linked with the standard library, the following can be used:
lc -Lm math2
THE LINKER:
The program BLINK provides a powerful linker for the programmer. Here are
the most important options available in this linker
After the name BLINK all files which are linked together appear after the
FROM argument (or ROOT, or even nothing). The name of the program to be
executed follows the TO argument. Library files to be searched are listed
after the LIBRARY argument. Some sample calls:
BLINK FROM a, b, c TO program
BLINK a+b+c TO program LIBRARY folder/d
BLINK ROOT a,b,c TO folder/prg LIBRARY system/lib, obj/special
Using the parameter WITH all options can be stored in a file just as in a
MAKE file. A linker call is then:
BLINK WITH file
In the FILE the options mentioned above contain a parameter in each new
line. For Example:
ROOT a,b,c
TO folder/prg
LIBRARY system/lib, obj/special
PAGE 272
----------------------------------------------------------------------------
APPENDIX D - THE AZTEC C COMPILER
C compiler manufacturers are constantly updating and improving their
compilers. For instruction on how to install the compiler, see the
documentation and any README files that came with the compiler.
The compiler with the name CC can be found in directory C. The call is very
simple:
cc file.c
The source file file.c is compiled and translated into asssembly code. This
code can be optimised by a machine language programmer. This file has the
name ctmpaxx.xxx where x is a number which differs from one call to the
next. It is best to look at the current directory because this name is
needed imnmediately.
Also Aztec uses symbolic names for devices which are listed as follows:
CLIB
INCLUDE
CCTEMP
CCTEMP determines where temporary files created by the C compiler will be
stored. CLIB indicates the path to the libraries, while INCLUDE is the path
for the header (.h) files. Examples are:
assign CLIB: df0:lib/
assign INCLUDE: df0:include/
assign CCTEMP: ram:
Various options can be placed in front of the name of the file to be
compiled. Here are the most important:
-Ipath:
With -I a pathname can be provided in which the Include files are assumed
to be. The search for these files is made only in the sub-directory. The
option is comparable with the assignment INCLUDE (see above).
NOTE: The pathname immediately follows the I without any additional spaces.
For example:
cc -Iram:includes/privat/
PAGE 273
----------------------------------------------------------------------------
+C
Creates longer code, since jumps within the program code are equipped with
32-bit commands instead of 16-bit commands which could have been used.
+D
Causes data to be stored in 32-bit format. This slows down data access and
increases memory requirements, but makes data segments of any desired
possible size (theoretically). During a "normal" data access with 16-bit
addressing , the user is limited to a maximum of 64k of data.
+L
Variables and constants of INT type are stored in 32-bit format. Because of
this the programs become (partially) Lattice compatible since this compiler
always uses 32 bits. Without this option 16 bits are sufficient for INT
numbers.
-D
Defines a constant. It corresponds to the define statement, but is
assigned during the call of the compiler. No space can follow this option
character. Example:
cc -DTESTRUN=1 file.c
The assignment corresponds to the define
#define TESTRUN 1
-S
Suppresses warnings. The warnings are only messages and the compiled
programs are usually capable of being run. To identify the real errors,
this option can be used to display only the error messages on the monitor.
+P
Causes the compiler to create Lattice compatible code. All data jumps and
INT numbers are automatically created in the 32-bit version.
THE ASSEMBLER:
After the C compiler comes the assembler which is named AS. It is also
stored in the C: directory. The call is similar to the C compiler.
as file.o
With the option -O a new name can be given to the new file. Example:
as -O program.o ctmpxyz.123
Additional options are only of interest to the assembler specialist. Since
this book deals with C programming, they will not be considered further. It
is only an intermediate step.
THE LINKER:
For Aztec the linker provided is called LN and can be found in the C:
sub-directory. The files to be linked together are placed one behind the
other. Whether they are libraries or modules doesn't matter in principle,
but libraries should be placed at the end of the list.
PAGE 274
----------------------------------------------------------------------------
ln file.o c.lib
The standard file c.lib is also like other libraries in the lib: directory.
Several modules can be linked together:
ln -o result modul1.o modul2.o modul3.o c.lib
The name assignment for the resulting program is performed with -O. The
linker can be informed about libraries with -L, but the extension of .lib
is then omitted. Example:
ln file.o -Lc -Lm
Two additional options are +C and +F which permit selection of special
storage areas. A code letter follows the option which has the following
significance:
c program
d initialised data
b data which was not initialised.
The +C stands for standard chip memory, +F for fast memory. These two
groups of RAM areas are especially important for graphic programming since
certain data must always be stored in chip memory. With this option the
following can be requested:
ln +Cdb +Fc file.o -Lc
This causes the storage of data in chip-memory and the storage of the
program in the normal fast memory. Without a special statement, all
information would have been stored in the fast memory area.
Here is a MAKE file which is tailored to Aztec C:
.key file
echo "Compiling <file$t1>.c "
cc -t <file$t1>.c
echo "Assembling <file$t1>.asm "
as <file$t1>.asm
echo "Linking <file$t1>.ams to <file$t1> "
ln <file$t1>.o -lm -lc
; -lm -lc is called :link clib and mathlib in addition.
echo "Everything clear !"
The file is named az-make on the optional program disk. Here is an example
call to compile, assemble and link a source file named array.c:
execute az-make array
PAGE 275
----------------------------------------------------------------------------
APPENDIX E - COMPILER RESERVED WORDS
Commands which are presented here, but were not described in the book,
either have no function in the current C compilers, or are reserved for
future version.
auto enum short
break extern sizeof
case float static
char for struct
continue goto switch
default if typedef
do int union
double long unsigned
else register void
entry return while
PAGE 276
----------------------------------------------------------------------------
APPENDIX F - OPERATOR PRECEDENCE.
Precedence Operator Description Evalution.
---------- -------- --------------------------- ---------
1 () function left to right
[] array left to right
. structure declaration left to right
-> structure declaration left to right
2 cast Forced type conversion right to left.
* content of right to left.
& address of right to left.
- Negative sign right to left.
! logical NOT right to left.
~ Bitwise complement. right to left.
++ Increment. right to left.
-- Decrement. right to left.
sizeof storage requirement right to left.
3 * multiplication left to right
/ division " "
% remainder (modulo) " "
4 + addition " "
- subtraction " "
5 > shift to right " "
< shift to left " "
6 < less than " "
> greater than " "
<= Less than or equal to " "
>= Greater than or equal to " "
7 == equal " "
!= unequal " "
8 & bitwise AND " "
9 ^ bitwise EXOR " "
10 | bitwise OR " "
11 && Logical AND " "
12 || Logical OR " "
13 ?: Conditional evaluation. right to left
14 = Assignment. " "
#= abbreviated assignment " "
15 , Separation of expressions left to right.
PAGE 277
----------------------------------------------------------------------------
APPENDIX G - STORAGE CLASSES
STORAGE CLASS VALIDITY DURATION.
------------- ---------- ----------
auto Block Block.
extern Program Program.
register Block Block
static (intern) Block Program
static (extern) Block Program
APPENDIX H - TYPE CONVERSIONS.
Rules:
1.) CHAR and SHORT are always converted to INT and FLOAT into DOUBLE.
2.) If after this conversion one of the operators should have the type
DOUBLE, the second operand and the result are also converted to double.
3.) If a data type is LONG, all participating values are also transformed
to LONG.
4.) If an UNSIGNED value is found among the operands, all values are
converted to UNSIGNED.
PAGE 278
----------------------------------------------------------------------------
APPENDIX I - MODES FOR FOPEN.
LATTICE C:
String Create Cut File Read Write Append Binary
-------------------------------------------------------------------
"r" no no yes no no yes
"w" yes yes no yes no yes
"a" yes no no no yes yes
"r+" no no yes yes no yes
"w+" yes no yes yes no yes
"a+" yes no yes no yes yes
"ra" no no yes no no no
"wa" yes yes no yes no no
"aa" yes no no no yes no
"ra+" no no yes yes no no
"wa+" yes no yes yes no no
"aa+" yes no yes no yes no
"rb" no no yes no no yes
"wb" yes yes no yes no yes
"ab" yes no no no yes yes
"rb+" no no yes yes no yes
"wb+" yes no yes yes no yes
"ab+" yes no yes no yes yes
No conversions are made for binary files. If the file was opened as an
ASCII file, which can be recognised by the "a" at the second place, all
carriage returns (code 13=\r) are eliminated and the character with the
ASCII code 26 is converted to EOF (-1) during reading. During writing, the
single line feed (\n), is converted to the character combination \r\n.
To differentiate the two modes, Lattice C uses an internal INT variable
named _fmode. If the highest value is set (_fmode & 0x8000), the binary
mode is used, or else the conversions indicated are performed.
CHANGES FOR AZTEC:
Aztec opens all files in binary. Aztec also offers the "x" and "x+" modes
which open a file for writing. If the file doesn't yet exist it is created,
With "x+" the file can be read and written after opening.
PAGE 279
----------------------------------------------------------------------------
INDEX
? conditional operator 163
Abbreviations 34,195
Activate 237
Addresses 131
AND 165
Arguments 28,105,203
Arrays 115,189
ASCII code 10,84
AUTO 143,155,181
Aztec C 19,39,41,47,65,210
Backslash 29,85
<backspace> key 18
BASIC 4,48
Bitfields 175
Bit manipulation 165
Bit shifting 167
Bit-planes 251
Bitwise shift operators 167
Braces 25,28
BREAK 123
Buffer 206
C language 4
Calculation 35,61
CASE statement 189
CAST operator 71
CHAR 39,57,68,111,115,133,136,
181,203
Char pointer 191
Character strings. 39
CLI 10,13,17,25,203,228
Comments 30,258
Comparison operators 45
Compiler 3-5,9,27,153
Complex Data Types 173
CON 219
Conditional Operator 163
Console 218
Constant 177
CONTINUE 123,125
Conversion program 82
CPU 206
CREATE 213
<ctrl><x> 18
Data types 105,182,231
De-referencing 133
Decimal notation 85
Declarations 99,181
Decrement Operator 97
DEFINE 174,206
Definition 99
Direct Access 216
Directives 229
Do ... While loop 49,58
DOS 257
DOUBLE 67,106,136,178
ED 10,91
Editor 3,9
ELSE 33
End of Paragraph 27
ENUM 177
EOF 209
Error Checking 49
Errors 189,196,231
<esc> key 18
Escape sequences 28
EXCLUSIVE OR 168
EXTERN 99,100,145,181
FGETC 211
File extension 10
File mode 208
FLOAT 67,115,136,146,173,181,195
Floating point numbers 37
Floating point variables 67
Flowchart 9
FOPEN 209
FOR 48,57,123
Format Specification 32,77
FPRINTF 211,220
FPUTC 211
FREAD 211
FSCANF 208,211
FSEEK 216
FTELL 217
Function 12,25,28,82,105,143,195
FWRITE 211
Gadget 235
GETC 206
GETCHAR 148,207,218
Global variables 155,156
GOTO 169
Guru Meditation 37
Header Files 91,206,236
Hexadecimal system 80,81
High Level Languages 4
Icons 17,227,235
IF 32-33,45,119
Increment operator 97
Index 58
Initialisation 99,184
INT 31,67,106,115,133,181,231
Integer Division 61
Integer variables 31
Integers 31
Interlace 252
Interpreted Languages 4,5
Intuition 235,238
ITOA 157
Keywords 25
Lattice C 13,38,41,46,65,81,91,212
Library Macros 198
Library 25,238
Linefeeds 27
Linker 3,9,12,27
Logical AND 52
Logical errors 13
Logical OR 52
LOGO 4
LONG 68,69,82,115,133,181
182,217
Loops 45
LSEEK 216
Machine Language 4
Macros 3,195,227
MAIN function 25,99,105,203
MAKE file 13,203
Menus 235
Modula 2 4
Modules 12
Modulo 61,157
Mouse 17,235
Multi-dimensional arrays 117
Multiple assignment 101
Multiple dimensions 156
Multitasking 235
Nocarerefresh 237
Number Conversion 82,86
Object Code 11
Octal system 80
One's complement 168
One-dimensional arrays 117
OPEN function 213,236
Operating system 235
OR 166
Pascal 4,29
Pointer arrays 189
Pointers 131,133,173,189,235
Precedence 61,136
Preferences 237
PreProcessor 89,227,229
PreProcessor directives 229
PRINTF 22,25,27,45,75,98,
124,157,189
Program format 27
PUTC 206
PUTCHAR 207
PUTS 189
RAW 220
Redirection 222
Referencing 133
Register 143,146,181
RETURN 106
<Return> key 18,40
Reverse 159
SCANF 31,75,131,134,208,218
Scanner 262
Screens 243
Semicolon 106
SHORT 68,69,181
SIZEOF 164,191,212
Smart_refresh 237
Source Code 28,96,189
SPRINTF 220
SQUARE 106
Standard input/output 218
Statement Block 34
STATIC variables 144,156,181
STDERR 223
STDIN 223
STDIO.H 91,148,163,223
STDOUT 223
STRCMP 154
STRCPY 109,135,147,190
Strings 57,178
STRLEN 111
STRUCT 173,236
Structures 183,235
SWITCH 123,125
Syntax errors 13
System crash 37
TELL 217
TEXT_ARR 182
Tips and tricks 227
Transportability 6
Type conversion 70
TYPEDEF 178
Unbuffered Input / Output 213
UNSIGNED 67,175,181
UNSIGNED INT 69
User-defined libraries 153
Variable Declaration 105
Variables 31,65,143,173
VOID 25,105,108
WHILE 45,83,97,98,109,123,154
Window flags 237
WINDOWDEPTH 237
WINDOWDRAG 237
Windows 235
WiINDOWSIZING 237
Word Processor 10
Workbench 227,228
* - * - * - * - * - * - * - * T H E E N D * - * - * - * - * - * - * - *
This File was Downloaded from ....... > T H E J A M < <
Running on A3000 (One Parted +2 Node's)
Node 1 : 49(0)201-626-047 Node 2 : 49(0)201-622-897
System-Operator : Selim and Rudi Co-Operator : Oli
This file passed through
__ __ __ __
|/\ |/\ /||_ || ||/\ | | /|| || \
|\/ |\/ |-| |_||-| |\/ | __|-| ||-
| | \ | | __||__|| \ |__|| | / |_/
L S D U N I T E D S T A T E S H E A D Q U A R T E R S
(501) 336-9661
long before you ever got it...